1 /*
2  * Copyright (C) 2019 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 static android.view.View.GONE;
20 
21 import static androidx.constraintlayout.widget.ConstraintSet.END;
22 import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
23 import static androidx.constraintlayout.widget.ConstraintSet.START;
24 import static androidx.constraintlayout.widget.ConstraintSet.TOP;
25 import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
26 
27 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
28 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
29 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
30 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
31 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
32 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
33 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
34 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
35 import static com.android.systemui.statusbar.StatusBarState.SHADE;
36 import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
37 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
38 import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_CLOSED;
39 import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPEN;
40 import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPENING;
41 
42 import static java.lang.Float.isNaN;
43 
44 import android.animation.Animator;
45 import android.animation.AnimatorListenerAdapter;
46 import android.animation.ValueAnimator;
47 import android.app.ActivityManager;
48 import android.app.Fragment;
49 import android.app.StatusBarManager;
50 import android.content.ContentResolver;
51 import android.content.pm.ResolveInfo;
52 import android.content.res.Configuration;
53 import android.content.res.Resources;
54 import android.database.ContentObserver;
55 import android.graphics.Canvas;
56 import android.graphics.Color;
57 import android.graphics.ColorFilter;
58 import android.graphics.Insets;
59 import android.graphics.Paint;
60 import android.graphics.PointF;
61 import android.graphics.Rect;
62 import android.graphics.Region;
63 import android.graphics.drawable.Drawable;
64 import android.hardware.biometrics.SensorLocationInternal;
65 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
66 import android.os.Bundle;
67 import android.os.Handler;
68 import android.os.PowerManager;
69 import android.os.SystemClock;
70 import android.os.UserManager;
71 import android.os.VibrationEffect;
72 import android.provider.Settings;
73 import android.transition.ChangeBounds;
74 import android.transition.TransitionManager;
75 import android.util.Log;
76 import android.util.MathUtils;
77 import android.view.LayoutInflater;
78 import android.view.MotionEvent;
79 import android.view.VelocityTracker;
80 import android.view.View;
81 import android.view.ViewGroup;
82 import android.view.ViewPropertyAnimator;
83 import android.view.ViewStub;
84 import android.view.ViewTreeObserver;
85 import android.view.WindowInsets;
86 import android.view.accessibility.AccessibilityEvent;
87 import android.view.accessibility.AccessibilityManager;
88 import android.view.accessibility.AccessibilityNodeInfo;
89 import android.widget.FrameLayout;
90 
91 import androidx.annotation.Nullable;
92 import androidx.constraintlayout.widget.ConstraintSet;
93 
94 import com.android.internal.annotations.VisibleForTesting;
95 import com.android.internal.jank.InteractionJankMonitor;
96 import com.android.internal.logging.MetricsLogger;
97 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
98 import com.android.internal.policy.ScreenDecorationsUtils;
99 import com.android.internal.policy.SystemBarUtils;
100 import com.android.internal.util.LatencyTracker;
101 import com.android.keyguard.KeyguardStatusView;
102 import com.android.keyguard.KeyguardStatusViewController;
103 import com.android.keyguard.KeyguardUnfoldTransition;
104 import com.android.keyguard.KeyguardUpdateMonitor;
105 import com.android.keyguard.LockIconViewController;
106 import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
107 import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
108 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
109 import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
110 import com.android.systemui.DejankUtils;
111 import com.android.systemui.Dependency;
112 import com.android.systemui.R;
113 import com.android.systemui.animation.ActivityLaunchAnimator;
114 import com.android.systemui.animation.Interpolators;
115 import com.android.systemui.animation.LaunchAnimator;
116 import com.android.systemui.biometrics.AuthController;
117 import com.android.systemui.classifier.Classifier;
118 import com.android.systemui.classifier.FalsingCollector;
119 import com.android.systemui.controls.dagger.ControlsComponent;
120 import com.android.systemui.dagger.qualifiers.DisplayId;
121 import com.android.systemui.dagger.qualifiers.Main;
122 import com.android.systemui.doze.DozeLog;
123 import com.android.systemui.flags.FeatureFlags;
124 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
125 import com.android.systemui.fragments.FragmentService;
126 import com.android.systemui.media.KeyguardMediaController;
127 import com.android.systemui.media.MediaDataManager;
128 import com.android.systemui.media.MediaHierarchyManager;
129 import com.android.systemui.model.SysUiState;
130 import com.android.systemui.navigationbar.NavigationModeController;
131 import com.android.systemui.plugins.FalsingManager;
132 import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
133 import com.android.systemui.plugins.qs.DetailAdapter;
134 import com.android.systemui.plugins.qs.QS;
135 import com.android.systemui.plugins.statusbar.StatusBarStateController;
136 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
137 import com.android.systemui.qs.QSDetailDisplayer;
138 import com.android.systemui.screenrecord.RecordingController;
139 import com.android.systemui.shared.system.QuickStepContract;
140 import com.android.systemui.statusbar.CommandQueue;
141 import com.android.systemui.statusbar.GestureRecorder;
142 import com.android.systemui.statusbar.KeyguardAffordanceView;
143 import com.android.systemui.statusbar.KeyguardIndicationController;
144 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
145 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
146 import com.android.systemui.statusbar.NotificationRemoteInputManager;
147 import com.android.systemui.statusbar.NotificationShadeDepthController;
148 import com.android.systemui.statusbar.NotificationShelfController;
149 import com.android.systemui.statusbar.PulseExpansionHandler;
150 import com.android.systemui.statusbar.RemoteInputController;
151 import com.android.systemui.statusbar.StatusBarState;
152 import com.android.systemui.statusbar.SysuiStatusBarStateController;
153 import com.android.systemui.statusbar.VibratorHelper;
154 import com.android.systemui.statusbar.events.PrivacyDotViewController;
155 import com.android.systemui.statusbar.notification.AnimatableProperty;
156 import com.android.systemui.statusbar.notification.ConversationNotificationManager;
157 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
158 import com.android.systemui.statusbar.notification.NotificationEntryManager;
159 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
160 import com.android.systemui.statusbar.notification.PropertyAnimator;
161 import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
162 import com.android.systemui.statusbar.notification.collection.ListEntry;
163 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
164 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
165 import com.android.systemui.statusbar.notification.collection.render.ShadeViewManager;
166 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
167 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
168 import com.android.systemui.statusbar.notification.row.ExpandableView;
169 import com.android.systemui.statusbar.notification.stack.AmbientState;
170 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
171 import com.android.systemui.statusbar.notification.stack.MediaHeaderView;
172 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
173 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
174 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
175 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
176 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
177 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
178 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
179 import com.android.systemui.statusbar.phone.panelstate.PanelState;
180 import com.android.systemui.statusbar.policy.ConfigurationController;
181 import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
182 import com.android.systemui.statusbar.policy.KeyguardStateController;
183 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
184 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
185 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
186 import com.android.systemui.unfold.SysUIUnfoldComponent;
187 import com.android.systemui.util.Utils;
188 import com.android.systemui.util.settings.SecureSettings;
189 import com.android.systemui.wallet.controller.QuickAccessWalletController;
190 import com.android.wm.shell.animation.FlingAnimationUtils;
191 
192 import java.io.FileDescriptor;
193 import java.io.PrintWriter;
194 import java.util.ArrayList;
195 import java.util.Collections;
196 import java.util.List;
197 import java.util.Optional;
198 import java.util.concurrent.Executor;
199 import java.util.function.Consumer;
200 
201 import javax.inject.Inject;
202 import javax.inject.Provider;
203 
204 @StatusBarComponent.StatusBarScope
205 public class NotificationPanelViewController extends PanelViewController {
206 
207     private static final boolean DEBUG = false;
208 
209     /**
210      * The parallax amount of the quick settings translation when dragging down the panel
211      */
212     private static final float QS_PARALLAX_AMOUNT = 0.175f;
213 
214     /**
215      * Fling expanding QS.
216      */
217     private static final int FLING_EXPAND = 0;
218 
219     /**
220      * Fling collapsing QS, potentially stopping when QS becomes QQS.
221      */
222     private static final int FLING_COLLAPSE = 1;
223 
224     /**
225      * Fling until QS is completely hidden.
226      */
227     private static final int FLING_HIDE = 2;
228     private static final long ANIMATION_DELAY_ICON_FADE_IN =
229             ActivityLaunchAnimator.TIMINGS.getTotalDuration()
230                     - CollapsedStatusBarFragment.FADE_IN_DURATION
231                     - CollapsedStatusBarFragment.FADE_IN_DELAY - 48;
232 
233     private final DozeParameters mDozeParameters;
234     private final OnHeightChangedListener mOnHeightChangedListener = new OnHeightChangedListener();
235     private final OnClickListener mOnClickListener = new OnClickListener();
236     private final OnOverscrollTopChangedListener
237             mOnOverscrollTopChangedListener =
238             new OnOverscrollTopChangedListener();
239     private final KeyguardAffordanceHelperCallback
240             mKeyguardAffordanceHelperCallback =
241             new KeyguardAffordanceHelperCallback();
242     private final OnEmptySpaceClickListener
243             mOnEmptySpaceClickListener =
244             new OnEmptySpaceClickListener();
245     private final MyOnHeadsUpChangedListener
246             mOnHeadsUpChangedListener =
247             new MyOnHeadsUpChangedListener();
248     private final HeightListener mHeightListener = new HeightListener();
249     private final ConfigurationListener mConfigurationListener = new ConfigurationListener();
250     private final SettingsChangeObserver mSettingsChangeObserver;
251 
252     @VisibleForTesting final StatusBarStateListener mStatusBarStateListener =
253             new StatusBarStateListener();
254     private final NotificationPanelView mView;
255     private final VibratorHelper mVibratorHelper;
256     private final MetricsLogger mMetricsLogger;
257     private final ActivityManager mActivityManager;
258     private final ConfigurationController mConfigurationController;
259     private final Provider<FlingAnimationUtils.Builder> mFlingAnimationUtilsBuilder;
260     private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
261     private final NotificationIconAreaController mNotificationIconAreaController;
262 
263     // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
264     // changed.
265     private static final int CAP_HEIGHT = 1456;
266     private static final int FONT_HEIGHT = 2163;
267 
268     /**
269      * Maximum time before which we will expand the panel even for slow motions when getting a
270      * touch passed over from launcher.
271      */
272     private static final int MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER = 300;
273 
274     private static final String COUNTER_PANEL_OPEN = "panel_open";
275     private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
276     private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
277 
278     private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
279     private static final Rect EMPTY_RECT = new Rect();
280 
281     private final LayoutInflater mLayoutInflater;
282     private final PowerManager mPowerManager;
283     private final AccessibilityManager mAccessibilityManager;
284     private final NotificationWakeUpCoordinator mWakeUpCoordinator;
285     private final PulseExpansionHandler mPulseExpansionHandler;
286     private final KeyguardBypassController mKeyguardBypassController;
287     private final KeyguardUpdateMonitor mUpdateMonitor;
288     private final ConversationNotificationManager mConversationNotificationManager;
289     private final AuthController mAuthController;
290     private final MediaHierarchyManager mMediaHierarchyManager;
291     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
292     private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
293     private final KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory;
294     private final KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
295     private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
296     private final QSDetailDisplayer mQSDetailDisplayer;
297     private final FragmentService mFragmentService;
298     private final ScrimController mScrimController;
299     private final PrivacyDotViewController mPrivacyDotViewController;
300     private final QuickAccessWalletController mQuickAccessWalletController;
301     private final ControlsComponent mControlsComponent;
302     private final NotificationRemoteInputManager mRemoteInputManager;
303 
304     // Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow card.
305     // If there are exactly 1 + mMaxKeyguardNotifications, then still shows all notifications
306     private final int mMaxKeyguardNotifications;
307     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
308     private final TapAgainViewController mTapAgainViewController;
309     private final SplitShadeHeaderController mSplitShadeHeaderController;
310     private final RecordingController mRecordingController;
311     private boolean mShouldUseSplitNotificationShade;
312     // The bottom padding reserved for elements of the keyguard measuring notifications
313     private float mKeyguardNotificationBottomPadding;
314     // Current max allowed keyguard notifications determined by measuring the panel
315     private int mMaxAllowedKeyguardNotifications;
316 
317     private ViewGroup mPreviewContainer;
318     private KeyguardAffordanceHelper mAffordanceHelper;
319     private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
320     private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
321     private KeyguardStatusBarView mKeyguardStatusBar;
322     private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
323     @VisibleForTesting QS mQs;
324     private FrameLayout mQsFrame;
325     private KeyguardStatusViewController mKeyguardStatusViewController;
326     private LockIconViewController mLockIconViewController;
327     private NotificationsQuickSettingsContainer mNotificationContainerParent;
328     private NotificationsQSContainerController mNotificationsQSContainerController;
329     private boolean mAnimateNextPositionUpdate;
330     private float mQuickQsOffsetHeight;
331     private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
332 
333     private int mTrackingPointer;
334     private VelocityTracker mQsVelocityTracker;
335     private boolean mQsTracking;
336 
337     /**
338      * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and
339      * the expansion for quick settings.
340      */
341     private boolean mConflictingQsExpansionGesture;
342 
343     private boolean mPanelExpanded;
344     private boolean mQsExpanded;
345     private boolean mQsExpandedWhenExpandingStarted;
346     private boolean mQsFullyExpanded;
347     private boolean mKeyguardShowing;
348     private boolean mKeyguardQsUserSwitchEnabled;
349     private boolean mKeyguardUserSwitcherEnabled;
350     private boolean mDozing;
351     private boolean mDozingOnDown;
352     private boolean mBouncerShowing;
353     private int mBarState;
354     private float mInitialHeightOnTouch;
355     private float mInitialTouchX;
356     private float mInitialTouchY;
357     private float mQsExpansionHeight;
358     private int mQsMinExpansionHeight;
359     private int mQsMaxExpansionHeight;
360     private int mQsPeekHeight;
361     private boolean mStackScrollerOverscrolling;
362     private boolean mQsExpansionFromOverscroll;
363     private float mLastOverscroll;
364     private boolean mQsExpansionEnabledPolicy = true;
365     private boolean mQsExpansionEnabledAmbient = true;
366     private ValueAnimator mQsExpansionAnimator;
367     private FlingAnimationUtils mFlingAnimationUtils;
368     private int mStatusBarMinHeight;
369     private int mStatusBarHeaderHeightKeyguard;
370     private float mOverStretchAmount;
371     private float mDownX;
372     private float mDownY;
373     private int mDisplayTopInset = 0; // in pixels
374     private int mDisplayRightInset = 0; // in pixels
375     private int mSplitShadeStatusBarHeight;
376 
377     private final KeyguardClockPositionAlgorithm
378             mClockPositionAlgorithm =
379             new KeyguardClockPositionAlgorithm();
380     private final KeyguardClockPositionAlgorithm.Result
381             mClockPositionResult =
382             new KeyguardClockPositionAlgorithm.Result();
383     private boolean mIsExpanding;
384 
385     private boolean mBlockTouches;
386     // Used for two finger gesture as well as accessibility shortcut to QS.
387     private boolean mQsExpandImmediate;
388     private boolean mTwoFingerQsExpandPossible;
389     private String mHeaderDebugInfo;
390 
391     /**
392      * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
393      * need to take this into account in our panel height calculation.
394      */
395     private boolean mQsAnimatorExpand;
396     private boolean mIsLaunchTransitionFinished;
397     private boolean mIsLaunchTransitionRunning;
398     private Runnable mLaunchAnimationEndRunnable;
399     private boolean mOnlyAffordanceInThisMotion;
400     private ValueAnimator mQsSizeChangeAnimator;
401 
402     private boolean mQsScrimEnabled = true;
403     private boolean mQsTouchAboveFalsingThreshold;
404     private int mQsFalsingThreshold;
405 
406     private HeadsUpTouchHelper mHeadsUpTouchHelper;
407     private boolean mListenForHeadsUp;
408     private int mNavigationBarBottomHeight;
409     private boolean mExpandingFromHeadsUp;
410     private boolean mCollapsedOnDown;
411     private int mPositionMinSideMargin;
412     private int mLastOrientation = -1;
413     private boolean mClosingWithAlphaFadeOut;
414     private boolean mHeadsUpAnimatingAway;
415     private boolean mLaunchingAffordance;
416     private boolean mAffordanceHasPreview;
417     private final FalsingManager mFalsingManager;
418     private final FalsingCollector mFalsingCollector;
419     private String mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
420 
421     private Runnable mHeadsUpExistenceChangedRunnable = () -> {
422         setHeadsUpAnimatingAway(false);
423         updatePanelExpansionAndVisibility();
424     };
425     // TODO (b/162832756): once migrated to the new pipeline, delete legacy group manager
426     private NotificationGroupManagerLegacy mGroupManager;
427     private boolean mShowIconsWhenExpanded;
428     private int mIndicationBottomPadding;
429     private int mAmbientIndicationBottomPadding;
430     private boolean mIsFullWidth;
431     private boolean mBlockingExpansionForCurrentTouch;
432 
433     // TODO (b/204204226): no longer needed once refactor is complete
434     private final boolean mUseCombinedQSHeaders;
435 
436     /**
437      * Following variables maintain state of events when input focus transfer may occur.
438      */
439     private boolean mExpectingSynthesizedDown; // expecting to see synthesized DOWN event
440     private boolean mLastEventSynthesizedDown; // last event was synthesized DOWN event
441 
442     /**
443      * Current dark amount that follows regular interpolation curve of animation.
444      */
445     private float mInterpolatedDarkAmount;
446 
447     /**
448      * Dark amount that animates from 0 to 1 or vice-versa in linear manner, even if the
449      * interpolation curve is different.
450      */
451     private float mLinearDarkAmount;
452 
453     private boolean mPulsing;
454     private boolean mUserSetupComplete;
455     private boolean mHideIconsDuringLaunchAnimation = true;
456     private int mStackScrollerMeasuringPass;
457     /**
458      * Non-null if there's a heads-up notification that we're currently tracking the position of.
459      */
460     @Nullable
461     private ExpandableNotificationRow mTrackedHeadsUpNotification;
462     private final ArrayList<Consumer<ExpandableNotificationRow>>
463             mTrackingHeadsUpListeners = new ArrayList<>();
464     private HeadsUpAppearanceController mHeadsUpAppearanceController;
465 
466     private int mPanelAlpha;
467     private Runnable mPanelAlphaEndAction;
468     private float mBottomAreaShadeAlpha;
469     private final ValueAnimator mBottomAreaShadeAlphaAnimator;
470     private final AnimatableProperty mPanelAlphaAnimator = AnimatableProperty.from("panelAlpha",
471             NotificationPanelView::setPanelAlphaInternal,
472             NotificationPanelView::getCurrentPanelAlpha,
473             R.id.panel_alpha_animator_tag, R.id.panel_alpha_animator_start_tag,
474             R.id.panel_alpha_animator_end_tag);
475     private final AnimationProperties mPanelAlphaOutPropertiesAnimator =
476             new AnimationProperties().setDuration(150).setCustomInterpolator(
477                     mPanelAlphaAnimator.getProperty(), Interpolators.ALPHA_OUT);
478     private final AnimationProperties mPanelAlphaInPropertiesAnimator =
479             new AnimationProperties().setDuration(200).setAnimationEndAction((property) -> {
480                             if (mPanelAlphaEndAction != null) {
481                                 mPanelAlphaEndAction.run();
482                             }
483                         }).setCustomInterpolator(
484                     mPanelAlphaAnimator.getProperty(), Interpolators.ALPHA_IN);
485     private final NotificationEntryManager mEntryManager;
486 
487     private final CommandQueue mCommandQueue;
488     private final NotificationLockscreenUserManager mLockscreenUserManager;
489     private final UserManager mUserManager;
490     private final MediaDataManager mMediaDataManager;
491     private final SysUiState mSysUiState;
492 
493     private NotificationShadeDepthController mDepthController;
494     private int mDisplayId;
495 
496     /**
497      * Cache the resource id of the theme to avoid unnecessary work in onThemeChanged.
498      *
499      * onThemeChanged is forced when the theme might not have changed. So, to avoid unncessary
500      * work, check the current id with the cached id.
501      */
502     private int mThemeResId;
503     private KeyguardIndicationController mKeyguardIndicationController;
504     private int mShelfHeight;
505     private int mDarkIconSize;
506     private int mHeadsUpInset;
507     private boolean mHeadsUpPinnedMode;
508     private boolean mAllowExpandForSmallExpansion;
509     private Runnable mExpandAfterLayoutRunnable;
510 
511     /**
512      * The padding between the start of notifications and the qs boundary on the lockscreen.
513      * On lockscreen, notifications aren't inset this extra amount, but we still want the
514      * qs boundary to be padded.
515      */
516     private int mLockscreenNotificationQSPadding;
517 
518     /**
519      * The amount of progress we are currently in if we're transitioning to the full shade.
520      * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
521      * shade. This value can also go beyond 1.1 when we're overshooting!
522      */
523     private float mTransitioningToFullShadeProgress;
524 
525     /**
526      * Position of the qs bottom during the full shade transition. This is needed as the toppadding
527      * can change during state changes, which makes it much harder to do animations
528      */
529     private int mTransitionToFullShadeQSPosition;
530 
531     /**
532      * Distance that the full shade transition takes in order for qs to fully transition to the
533      * shade.
534      */
535     private int mDistanceForQSFullShadeTransition;
536 
537     /**
538      * The translation amount for QS for the full shade transition
539      */
540     private float mQsTranslationForFullShadeTransition;
541 
542     /**
543      * The maximum overshoot allowed for the top padding for the full shade transition
544      */
545     private int mMaxOverscrollAmountForPulse;
546 
547     /**
548      * Should we animate the next bounds update
549      */
550     private boolean mAnimateNextNotificationBounds;
551     /**
552      * The delay for the next bounds animation
553      */
554     private long mNotificationBoundsAnimationDelay;
555 
556     /**
557      * The duration of the notification bounds animation
558      */
559     private long mNotificationBoundsAnimationDuration;
560 
561     /**
562      * Is this a collapse that started on the panel where we should allow the panel to intercept
563      */
564     private boolean mIsPanelCollapseOnQQS;
565 
566     private boolean mAnimatingQS;
567 
568     /**
569      * The end bounds of a clipping animation.
570      */
571     private final Rect mQsClippingAnimationEndBounds = new Rect();
572 
573     /**
574      * The animator for the qs clipping bounds.
575      */
576     private ValueAnimator mQsClippingAnimation = null;
577 
578     /**
579      * Is the current animator resetting the qs translation.
580      */
581     private boolean mIsQsTranslationResetAnimator;
582 
583     /**
584      * Is the current animator resetting the pulse expansion after a drag down
585      */
586     private boolean mIsPulseExpansionResetAnimator;
587     private final Rect mKeyguardStatusAreaClipBounds = new Rect();
588     private final Region mQsInterceptRegion = new Region();
589 
590     /**
591      * The alpha of the views which only show on the keyguard but not in shade / shade locked
592      */
593     private float mKeyguardOnlyContentAlpha = 1.0f;
594 
595     private float mUdfpsMaxYBurnInOffset;
596 
597     /**
598      * Are we currently in gesture navigation
599      */
600     private boolean mIsGestureNavigation;
601     private int mOldLayoutDirection;
602     private NotificationShelfController mNotificationShelfController;
603     private int mScrimCornerRadius;
604     private int mScreenCornerRadius;
605     private boolean mQSAnimatingHiddenFromCollapsed;
606 
607     private int mQsClipTop;
608     private int mQsClipBottom;
609     private boolean mQsVisible;
610     private final ContentResolver mContentResolver;
611     private float mMinFraction;
612 
613     private final Executor mUiExecutor;
614     private final SecureSettings mSecureSettings;
615 
616     private KeyguardMediaController mKeyguardMediaController;
617 
618     private boolean mStatusViewCentered = true;
619 
620     private Optional<KeyguardUnfoldTransition> mKeyguardUnfoldTransition;
621 
622     private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() {
623         @Override
624         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
625             super.onInitializeAccessibilityNodeInfo(host, info);
626             info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
627             info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
628         }
629 
630         @Override
631         public boolean performAccessibilityAction(View host, int action, Bundle args) {
632             if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId()
633                     || action
634                     == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId()) {
635                 mStatusBarKeyguardViewManager.showBouncer(true);
636                 return true;
637             }
638             return super.performAccessibilityAction(host, action, args);
639         }
640     };
641 
642     private final FalsingTapListener mFalsingTapListener = new FalsingTapListener() {
643         @Override
644         public void onDoubleTapRequired() {
645             if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
646                 mTapAgainViewController.show();
647             } else {
648                 mKeyguardIndicationController.showTransientIndication(
649                         R.string.notification_tap_again);
650             }
651             mVibratorHelper.vibrate(VibrationEffect.EFFECT_STRENGTH_MEDIUM);
652         }
653     };
654 
655     @Inject
NotificationPanelViewController(NotificationPanelView view, @Main Resources resources, @Main Handler handler, LayoutInflater layoutInflater, NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler, DynamicPrivacyController dynamicPrivacyController, KeyguardBypassController bypassController, FalsingManager falsingManager, FalsingCollector falsingCollector, NotificationLockscreenUserManager notificationLockscreenUserManager, NotificationEntryManager notificationEntryManager, KeyguardStateController keyguardStateController, StatusBarStateController statusBarStateController, DozeLog dozeLog, DozeParameters dozeParameters, CommandQueue commandQueue, VibratorHelper vibratorHelper, LatencyTracker latencyTracker, PowerManager powerManager, AccessibilityManager accessibilityManager, @DisplayId int displayId, KeyguardUpdateMonitor keyguardUpdateMonitor, MetricsLogger metricsLogger, ActivityManager activityManager, ConfigurationController configurationController, Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder, StatusBarTouchableRegionManager statusBarTouchableRegionManager, ConversationNotificationManager conversationNotificationManager, MediaHierarchyManager mediaHierarchyManager, StatusBarKeyguardViewManager statusBarKeyguardViewManager, NotificationsQSContainerController notificationsQSContainerController, NotificationStackScrollLayoutController notificationStackScrollLayoutController, KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory, KeyguardQsUserSwitchComponent.Factory keyguardQsUserSwitchComponentFactory, KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory, KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory, LockscreenShadeTransitionController lockscreenShadeTransitionController, QSDetailDisplayer qsDetailDisplayer, NotificationGroupManagerLegacy groupManager, NotificationIconAreaController notificationIconAreaController, AuthController authController, ScrimController scrimController, UserManager userManager, MediaDataManager mediaDataManager, NotificationShadeDepthController notificationShadeDepthController, AmbientState ambientState, LockIconViewController lockIconViewController, KeyguardMediaController keyguardMediaController, PrivacyDotViewController privacyDotViewController, TapAgainViewController tapAgainViewController, NavigationModeController navigationModeController, FragmentService fragmentService, ContentResolver contentResolver, QuickAccessWalletController quickAccessWalletController, RecordingController recordingController, @Main Executor uiExecutor, SecureSettings secureSettings, SplitShadeHeaderController splitShadeHeaderController, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, LockscreenGestureLogger lockscreenGestureLogger, PanelExpansionStateManager panelExpansionStateManager, NotificationRemoteInputManager remoteInputManager, Optional<SysUIUnfoldComponent> unfoldComponent, ControlsComponent controlsComponent, FeatureFlags featureFlags)656     public NotificationPanelViewController(NotificationPanelView view,
657             @Main Resources resources,
658             @Main Handler handler,
659             LayoutInflater layoutInflater,
660             NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler,
661             DynamicPrivacyController dynamicPrivacyController,
662             KeyguardBypassController bypassController, FalsingManager falsingManager,
663             FalsingCollector falsingCollector,
664             NotificationLockscreenUserManager notificationLockscreenUserManager,
665             NotificationEntryManager notificationEntryManager,
666             KeyguardStateController keyguardStateController,
667             StatusBarStateController statusBarStateController, DozeLog dozeLog,
668             DozeParameters dozeParameters, CommandQueue commandQueue, VibratorHelper vibratorHelper,
669             LatencyTracker latencyTracker, PowerManager powerManager,
670             AccessibilityManager accessibilityManager, @DisplayId int displayId,
671             KeyguardUpdateMonitor keyguardUpdateMonitor, MetricsLogger metricsLogger,
672             ActivityManager activityManager,
673             ConfigurationController configurationController,
674             Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder,
675             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
676             ConversationNotificationManager conversationNotificationManager,
677             MediaHierarchyManager mediaHierarchyManager,
678             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
679             NotificationsQSContainerController notificationsQSContainerController,
680             NotificationStackScrollLayoutController notificationStackScrollLayoutController,
681             KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
682             KeyguardQsUserSwitchComponent.Factory keyguardQsUserSwitchComponentFactory,
683             KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
684             KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory,
685             LockscreenShadeTransitionController lockscreenShadeTransitionController,
686             QSDetailDisplayer qsDetailDisplayer,
687             NotificationGroupManagerLegacy groupManager,
688             NotificationIconAreaController notificationIconAreaController,
689             AuthController authController,
690             ScrimController scrimController,
691             UserManager userManager,
692             MediaDataManager mediaDataManager,
693             NotificationShadeDepthController notificationShadeDepthController,
694             AmbientState ambientState,
695             LockIconViewController lockIconViewController,
696             KeyguardMediaController keyguardMediaController,
697             PrivacyDotViewController privacyDotViewController,
698             TapAgainViewController tapAgainViewController,
699             NavigationModeController navigationModeController,
700             FragmentService fragmentService,
701             ContentResolver contentResolver,
702             QuickAccessWalletController quickAccessWalletController,
703             RecordingController recordingController,
704             @Main Executor uiExecutor,
705             SecureSettings secureSettings,
706             SplitShadeHeaderController splitShadeHeaderController,
707             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
708             LockscreenGestureLogger lockscreenGestureLogger,
709             PanelExpansionStateManager panelExpansionStateManager,
710             NotificationRemoteInputManager remoteInputManager,
711             Optional<SysUIUnfoldComponent> unfoldComponent,
712             ControlsComponent controlsComponent,
713             FeatureFlags featureFlags) {
714         super(view,
715                 falsingManager,
716                 dozeLog,
717                 keyguardStateController,
718                 (SysuiStatusBarStateController) statusBarStateController,
719                 vibratorHelper,
720                 statusBarKeyguardViewManager,
721                 latencyTracker,
722                 flingAnimationUtilsBuilder.get(),
723                 statusBarTouchableRegionManager,
724                 lockscreenGestureLogger,
725                 panelExpansionStateManager,
726                 ambientState);
727         mView = view;
728         mVibratorHelper = vibratorHelper;
729         mKeyguardMediaController = keyguardMediaController;
730         mPrivacyDotViewController = privacyDotViewController;
731         mQuickAccessWalletController = quickAccessWalletController;
732         mControlsComponent = controlsComponent;
733         mMetricsLogger = metricsLogger;
734         mActivityManager = activityManager;
735         mConfigurationController = configurationController;
736         mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
737         mMediaHierarchyManager = mediaHierarchyManager;
738         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
739         mNotificationsQSContainerController = notificationsQSContainerController;
740         mNotificationsQSContainerController.init();
741         mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
742         mGroupManager = groupManager;
743         mNotificationIconAreaController = notificationIconAreaController;
744         mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
745         mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory;
746         mDepthController = notificationShadeDepthController;
747         mContentResolver = contentResolver;
748         mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory;
749         mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory;
750         mQSDetailDisplayer = qsDetailDisplayer;
751         mFragmentService = fragmentService;
752         mSettingsChangeObserver = new SettingsChangeObserver(handler);
753         mShouldUseSplitNotificationShade =
754                 Utils.shouldUseSplitNotificationShade(mResources);
755         mView.setWillNotDraw(!DEBUG);
756         mSplitShadeHeaderController = splitShadeHeaderController;
757         mLayoutInflater = layoutInflater;
758         mFalsingManager = falsingManager;
759         mFalsingCollector = falsingCollector;
760         mPowerManager = powerManager;
761         mWakeUpCoordinator = coordinator;
762         mAccessibilityManager = accessibilityManager;
763         mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
764         setPanelAlpha(255, false /* animate */);
765         mCommandQueue = commandQueue;
766         mRecordingController = recordingController;
767         mDisplayId = displayId;
768         mPulseExpansionHandler = pulseExpansionHandler;
769         mDozeParameters = dozeParameters;
770         mScrimController = scrimController;
771         mScrimController.setClipsQsScrim(!mShouldUseSplitNotificationShade);
772         mUserManager = userManager;
773         mMediaDataManager = mediaDataManager;
774         mTapAgainViewController = tapAgainViewController;
775         mUiExecutor = uiExecutor;
776         mSecureSettings = secureSettings;
777         // TODO: inject via dagger instead of Dependency
778         mSysUiState = Dependency.get(SysUiState.class);
779         pulseExpansionHandler.setPulseExpandAbortListener(() -> {
780             if (mQs != null) {
781                 mQs.animateHeaderSlidingOut();
782             }
783         });
784         mThemeResId = mView.getContext().getThemeResId();
785         mKeyguardBypassController = bypassController;
786         mUpdateMonitor = keyguardUpdateMonitor;
787         mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
788         lockscreenShadeTransitionController.setNotificationPanelController(this);
789         DynamicPrivacyControlListener
790                 dynamicPrivacyControlListener =
791                 new DynamicPrivacyControlListener();
792         dynamicPrivacyController.addListener(dynamicPrivacyControlListener);
793 
794         panelExpansionStateManager.addStateListener(this::onPanelStateChanged);
795 
796         mBottomAreaShadeAlphaAnimator = ValueAnimator.ofFloat(1f, 0);
797         mBottomAreaShadeAlphaAnimator.addUpdateListener(animation -> {
798             mBottomAreaShadeAlpha = (float) animation.getAnimatedValue();
799             updateKeyguardBottomAreaAlpha();
800         });
801         mBottomAreaShadeAlphaAnimator.setDuration(160);
802         mBottomAreaShadeAlphaAnimator.setInterpolator(Interpolators.ALPHA_OUT);
803         mLockscreenUserManager = notificationLockscreenUserManager;
804         mEntryManager = notificationEntryManager;
805         mConversationNotificationManager = conversationNotificationManager;
806         mAuthController = authController;
807         mLockIconViewController = lockIconViewController;
808         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
809         mRemoteInputManager = remoteInputManager;
810 
811         int currentMode = navigationModeController.addListener(
812                 mode -> mIsGestureNavigation = QuickStepContract.isGesturalMode(mode));
813         mIsGestureNavigation = QuickStepContract.isGesturalMode(currentMode);
814 
815         mView.setBackgroundColor(Color.TRANSPARENT);
816         OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener();
817         mView.addOnAttachStateChangeListener(onAttachStateChangeListener);
818         if (mView.isAttachedToWindow()) {
819             onAttachStateChangeListener.onViewAttachedToWindow(mView);
820         }
821 
822         mView.setOnApplyWindowInsetsListener(new OnApplyWindowInsetsListener());
823 
824         if (DEBUG) {
825             mView.getOverlay().add(new DebugDrawable());
826         }
827 
828         mMaxKeyguardNotifications = resources.getInteger(R.integer.keyguard_max_notification_count);
829         mKeyguardUnfoldTransition = unfoldComponent.map(c -> c.getKeyguardUnfoldTransition());
830         updateUserSwitcherFlags();
831         onFinishInflate();
832 
833         mUseCombinedQSHeaders = featureFlags.useCombinedQSHeaders();
834     }
835 
onFinishInflate()836     private void onFinishInflate() {
837         loadDimens();
838         mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
839 
840         FrameLayout userAvatarContainer = null;
841         KeyguardUserSwitcherView keyguardUserSwitcherView = null;
842 
843         if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled()) {
844             if (mKeyguardQsUserSwitchEnabled) {
845                 ViewStub stub = mView.findViewById(R.id.keyguard_qs_user_switch_stub);
846                 userAvatarContainer = (FrameLayout) stub.inflate();
847             } else {
848                 ViewStub stub = mView.findViewById(R.id.keyguard_user_switcher_stub);
849                 keyguardUserSwitcherView = (KeyguardUserSwitcherView) stub.inflate();
850             }
851         }
852 
853         mKeyguardStatusBarViewController =
854                 mKeyguardStatusBarViewComponentFactory.build(
855                         mKeyguardStatusBar,
856                         mNotificationPanelViewStateProvider)
857                         .getKeyguardStatusBarViewController();
858         mKeyguardStatusBarViewController.init();
859 
860         mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
861         updateViewControllers(
862                 mView.findViewById(R.id.keyguard_status_view),
863                 userAvatarContainer,
864                 keyguardUserSwitcherView);
865 
866         NotificationStackScrollLayout stackScrollLayout = mView.findViewById(
867                 R.id.notification_stack_scroller);
868         mNotificationStackScrollLayoutController.attach(stackScrollLayout);
869         mNotificationStackScrollLayoutController.setOnHeightChangedListener(
870                 mOnHeightChangedListener);
871         mNotificationStackScrollLayoutController.setOverscrollTopChangedListener(
872                 mOnOverscrollTopChangedListener);
873         mNotificationStackScrollLayoutController.setOnScrollListener(this::onNotificationScrolled);
874         mNotificationStackScrollLayoutController.setOnStackYChanged(this::onStackYChanged);
875         mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener(
876                 mOnEmptySpaceClickListener);
877         addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp);
878         mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area);
879         mPreviewContainer = mView.findViewById(R.id.preview_container);
880         mKeyguardBottomArea.setPreviewContainer(mPreviewContainer);
881         mLastOrientation = mResources.getConfiguration().orientation;
882 
883         initBottomArea();
884 
885         mWakeUpCoordinator.setStackScroller(mNotificationStackScrollLayoutController);
886         mQsFrame = mView.findViewById(R.id.qs_frame);
887         mPulseExpansionHandler.setUp(mNotificationStackScrollLayoutController);
888         mWakeUpCoordinator.addListener(new NotificationWakeUpCoordinator.WakeUpListener() {
889             @Override
890             public void onFullyHiddenChanged(boolean isFullyHidden) {
891                 mKeyguardStatusBarViewController.updateForHeadsUp();
892             }
893 
894             @Override
895             public void onPulseExpansionChanged(boolean expandingChanged) {
896                 if (mKeyguardBypassController.getBypassEnabled()) {
897                     // Position the notifications while dragging down while pulsing
898                     requestScrollerTopPaddingUpdate(false /* animate */);
899                 }
900             }
901         });
902 
903         mView.setRtlChangeListener(layoutDirection -> {
904             if (layoutDirection != mOldLayoutDirection) {
905                 mAffordanceHelper.onRtlPropertiesChanged();
906                 mOldLayoutDirection = layoutDirection;
907             }
908         });
909 
910         mView.setAccessibilityDelegate(mAccessibilityDelegate);
911         if (mShouldUseSplitNotificationShade) {
912             updateResources();
913         }
914 
915         mTapAgainViewController.init();
916         mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
917     }
918 
919     @Override
loadDimens()920     protected void loadDimens() {
921         super.loadDimens();
922         mFlingAnimationUtils = mFlingAnimationUtilsBuilder.get()
923                 .setMaxLengthSeconds(0.4f).build();
924         mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mView.getContext());
925         mStatusBarHeaderHeightKeyguard = Utils.getStatusBarHeaderHeightKeyguard(mView.getContext());
926         mQsPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height);
927         mClockPositionAlgorithm.loadDimens(mResources);
928         mQsFalsingThreshold = mResources.getDimensionPixelSize(R.dimen.qs_falsing_threshold);
929         mPositionMinSideMargin = mResources.getDimensionPixelSize(
930                 R.dimen.notification_panel_min_side_margin);
931         mIndicationBottomPadding = mResources.getDimensionPixelSize(
932                 R.dimen.keyguard_indication_bottom_padding);
933         mShelfHeight = mResources.getDimensionPixelSize(R.dimen.notification_shelf_height);
934         mDarkIconSize = mResources.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size_dark);
935         int statusbarHeight = SystemBarUtils.getStatusBarHeight(mView.getContext());
936         mHeadsUpInset = statusbarHeight + mResources.getDimensionPixelSize(
937                 R.dimen.heads_up_status_bar_padding);
938         mDistanceForQSFullShadeTransition = mResources.getDimensionPixelSize(
939                 R.dimen.lockscreen_shade_qs_transition_distance);
940         mMaxOverscrollAmountForPulse = mResources.getDimensionPixelSize(
941                 R.dimen.pulse_expansion_max_top_overshoot);
942         mScrimCornerRadius = mResources.getDimensionPixelSize(
943                 R.dimen.notification_scrim_corner_radius);
944         mScreenCornerRadius = (int) ScreenDecorationsUtils.getWindowCornerRadius(
945                 mView.getContext());
946         mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize(
947                 R.dimen.notification_side_paddings);
948         mUdfpsMaxYBurnInOffset = mResources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
949     }
950 
updateViewControllers(KeyguardStatusView keyguardStatusView, FrameLayout userAvatarView, KeyguardUserSwitcherView keyguardUserSwitcherView)951     private void updateViewControllers(KeyguardStatusView keyguardStatusView,
952             FrameLayout userAvatarView,
953             KeyguardUserSwitcherView keyguardUserSwitcherView) {
954         // Re-associate the KeyguardStatusViewController
955         KeyguardStatusViewComponent statusViewComponent =
956                 mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
957         mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
958         mKeyguardStatusViewController.init();
959 
960         if (mKeyguardUserSwitcherController != null) {
961             // Try to close the switcher so that callbacks are triggered if necessary.
962             // Otherwise, NPV can get into a state where some of the views are still hidden
963             mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(false);
964         }
965 
966         mKeyguardQsUserSwitchController = null;
967         mKeyguardUserSwitcherController = null;
968 
969         // Re-associate the KeyguardUserSwitcherController
970         if (userAvatarView != null) {
971             KeyguardQsUserSwitchComponent userSwitcherComponent =
972                     mKeyguardQsUserSwitchComponentFactory.build(userAvatarView);
973             mKeyguardQsUserSwitchController =
974                     userSwitcherComponent.getKeyguardQsUserSwitchController();
975             mKeyguardQsUserSwitchController.setNotificationPanelViewController(this);
976             mKeyguardQsUserSwitchController.init();
977             mKeyguardStatusBarViewController.setKeyguardUserSwitcherEnabled(true);
978         } else if (keyguardUserSwitcherView != null) {
979             KeyguardUserSwitcherComponent userSwitcherComponent =
980                     mKeyguardUserSwitcherComponentFactory.build(keyguardUserSwitcherView);
981             mKeyguardUserSwitcherController =
982                     userSwitcherComponent.getKeyguardUserSwitcherController();
983             mKeyguardUserSwitcherController.init();
984             mKeyguardStatusBarViewController.setKeyguardUserSwitcherEnabled(true);
985         } else {
986             mKeyguardStatusBarViewController.setKeyguardUserSwitcherEnabled(false);
987         }
988     }
989 
990     /**
991      * Returns if there's a custom clock being presented.
992      */
hasCustomClock()993     public boolean hasCustomClock() {
994         return mKeyguardStatusViewController.hasCustomClock();
995     }
996 
setStatusBar(StatusBar bar)997     private void setStatusBar(StatusBar bar) {
998         // TODO: this can be injected.
999         mStatusBar = bar;
1000         mKeyguardBottomArea.setStatusBar(mStatusBar);
1001     }
1002 
updateResources()1003     public void updateResources() {
1004         mQuickQsOffsetHeight = SystemBarUtils.getQuickQsOffsetHeight(mView.getContext());
1005         mSplitShadeStatusBarHeight = Utils.getSplitShadeStatusBarHeight(mView.getContext());
1006         int qsWidth = mResources.getDimensionPixelSize(R.dimen.qs_panel_width);
1007         int panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
1008         mShouldUseSplitNotificationShade =
1009                 Utils.shouldUseSplitNotificationShade(mResources);
1010         mScrimController.setClipsQsScrim(!mShouldUseSplitNotificationShade);
1011         if (mQs != null) {
1012             mQs.setInSplitShade(mShouldUseSplitNotificationShade);
1013         }
1014 
1015         int topMargin = mShouldUseSplitNotificationShade ? mSplitShadeStatusBarHeight :
1016                 mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
1017         mSplitShadeHeaderController.setSplitShadeMode(mShouldUseSplitNotificationShade);
1018 
1019         // To change the constraints at runtime, all children of the ConstraintLayout must have ids
1020         ensureAllViewsHaveIds(mNotificationContainerParent);
1021         ConstraintSet constraintSet = new ConstraintSet();
1022         constraintSet.clone(mNotificationContainerParent);
1023         if (mShouldUseSplitNotificationShade) {
1024             // width = 0 to take up all available space within constraints
1025             qsWidth = 0;
1026             panelWidth = 0;
1027             constraintSet.connect(R.id.qs_frame, END, R.id.qs_edge_guideline, END);
1028             constraintSet.connect(
1029                     R.id.notification_stack_scroller, START,
1030                     R.id.qs_edge_guideline, START);
1031             constraintSet.constrainHeight(R.id.split_shade_status_bar, mSplitShadeStatusBarHeight);
1032         } else {
1033             constraintSet.connect(R.id.qs_frame, END, PARENT_ID, END);
1034             constraintSet.connect(R.id.notification_stack_scroller, START, PARENT_ID, START);
1035             if (mUseCombinedQSHeaders) {
1036                 constraintSet.constrainHeight(R.id.split_shade_status_bar, WRAP_CONTENT);
1037             }
1038         }
1039         constraintSet.getConstraint(R.id.notification_stack_scroller).layout.mWidth = panelWidth;
1040         constraintSet.getConstraint(R.id.qs_frame).layout.mWidth = qsWidth;
1041         constraintSet.setMargin(R.id.notification_stack_scroller, TOP, topMargin);
1042         constraintSet.setMargin(R.id.qs_frame, TOP, topMargin);
1043         constraintSet.applyTo(mNotificationContainerParent);
1044         mAmbientState.setStackTopMargin(topMargin);
1045         mNotificationsQSContainerController.setSplitShadeEnabled(mShouldUseSplitNotificationShade);
1046 
1047         updateKeyguardStatusViewAlignment(/* animate= */false);
1048 
1049         mKeyguardMediaController.refreshMediaPosition();
1050     }
1051 
ensureAllViewsHaveIds(ViewGroup parentView)1052     private static void ensureAllViewsHaveIds(ViewGroup parentView) {
1053         for (int i = 0; i < parentView.getChildCount(); i++) {
1054             View childView = parentView.getChildAt(i);
1055             if (childView.getId() == View.NO_ID) {
1056                 childView.setId(View.generateViewId());
1057             }
1058         }
1059     }
1060 
reInflateStub(int viewId, int stubId, int layoutId, boolean enabled)1061     private View reInflateStub(int viewId, int stubId, int layoutId, boolean enabled) {
1062         View view = mView.findViewById(viewId);
1063         if (view != null) {
1064             int index = mView.indexOfChild(view);
1065             mView.removeView(view);
1066             if (enabled) {
1067                 view = mLayoutInflater.inflate(layoutId, mView, false);
1068                 mView.addView(view, index);
1069             } else {
1070                 // Add the stub back so we can re-inflate it again if necessary
1071                 ViewStub stub = new ViewStub(mView.getContext(), layoutId);
1072                 stub.setId(stubId);
1073                 mView.addView(stub, index);
1074                 view = null;
1075             }
1076         } else if (enabled) {
1077             // It's possible the stub was never inflated if the configuration changed
1078             ViewStub stub = mView.findViewById(stubId);
1079             view = stub.inflate();
1080         }
1081         return view;
1082     }
1083 
reInflateViews()1084     private void reInflateViews() {
1085         if (DEBUG) Log.d(TAG, "reInflateViews");
1086         // Re-inflate the status view group.
1087         KeyguardStatusView keyguardStatusView =
1088                 mNotificationContainerParent.findViewById(R.id.keyguard_status_view);
1089         int statusIndex = mNotificationContainerParent.indexOfChild(keyguardStatusView);
1090         mNotificationContainerParent.removeView(keyguardStatusView);
1091         keyguardStatusView = (KeyguardStatusView) mLayoutInflater.inflate(
1092                 R.layout.keyguard_status_view, mNotificationContainerParent, false);
1093         mNotificationContainerParent.addView(keyguardStatusView, statusIndex);
1094         // When it's reinflated, this is centered by default. If it shouldn't be, this will update
1095         // below when resources are updated.
1096         mStatusViewCentered = true;
1097         attachSplitShadeMediaPlayerContainer(
1098                 keyguardStatusView.findViewById(R.id.status_view_media_container));
1099 
1100         // we need to update KeyguardStatusView constraints after reinflating it
1101         updateResources();
1102 
1103         // Re-inflate the keyguard user switcher group.
1104         updateUserSwitcherFlags();
1105         boolean isUserSwitcherEnabled = mUserManager.isUserSwitcherEnabled();
1106         boolean showQsUserSwitch = mKeyguardQsUserSwitchEnabled && isUserSwitcherEnabled;
1107         boolean showKeyguardUserSwitcher =
1108                 !mKeyguardQsUserSwitchEnabled
1109                         && mKeyguardUserSwitcherEnabled
1110                         && isUserSwitcherEnabled;
1111         FrameLayout userAvatarView = (FrameLayout) reInflateStub(
1112                 R.id.keyguard_qs_user_switch_view /* viewId */,
1113                 R.id.keyguard_qs_user_switch_stub /* stubId */,
1114                 R.layout.keyguard_qs_user_switch /* layoutId */,
1115                 showQsUserSwitch /* enabled */);
1116         KeyguardUserSwitcherView keyguardUserSwitcherView =
1117                 (KeyguardUserSwitcherView) reInflateStub(
1118                         R.id.keyguard_user_switcher_view /* viewId */,
1119                         R.id.keyguard_user_switcher_stub /* stubId */,
1120                         R.layout.keyguard_user_switcher /* layoutId */,
1121                         showKeyguardUserSwitcher /* enabled */);
1122 
1123         updateViewControllers(mView.findViewById(R.id.keyguard_status_view), userAvatarView,
1124                 keyguardUserSwitcherView);
1125 
1126         // Update keyguard bottom area
1127         int index = mView.indexOfChild(mKeyguardBottomArea);
1128         mView.removeView(mKeyguardBottomArea);
1129         KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
1130         mKeyguardBottomArea = (KeyguardBottomAreaView) mLayoutInflater.inflate(
1131                 R.layout.keyguard_bottom_area, mView, false);
1132         mKeyguardBottomArea.initFrom(oldBottomArea);
1133         mKeyguardBottomArea.setPreviewContainer(mPreviewContainer);
1134         mView.addView(mKeyguardBottomArea, index);
1135         initBottomArea();
1136         mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
1137         mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
1138                 mStatusBarStateController.getInterpolatedDozeAmount());
1139 
1140         mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
1141                 mBarState,
1142                 false,
1143                 false,
1144                 mBarState);
1145         if (mKeyguardQsUserSwitchController != null) {
1146             mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
1147                     mBarState,
1148                     false,
1149                     false,
1150                     mBarState);
1151         }
1152         if (mKeyguardUserSwitcherController != null) {
1153             mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
1154                     mBarState,
1155                     false,
1156                     false,
1157                     mBarState);
1158         }
1159         setKeyguardBottomAreaVisibility(mBarState, false);
1160 
1161         mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
1162     }
1163 
attachSplitShadeMediaPlayerContainer(FrameLayout container)1164     private void attachSplitShadeMediaPlayerContainer(FrameLayout container) {
1165         mKeyguardMediaController.attachSplitShadeContainer(container);
1166     }
1167 
initBottomArea()1168     private void initBottomArea() {
1169         mAffordanceHelper = new KeyguardAffordanceHelper(
1170                 mKeyguardAffordanceHelperCallback, mView.getContext(), mFalsingManager);
1171         mKeyguardBottomArea.setAffordanceHelper(mAffordanceHelper);
1172         mKeyguardBottomArea.setStatusBar(mStatusBar);
1173         mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete);
1174         mKeyguardBottomArea.setFalsingManager(mFalsingManager);
1175         mKeyguardBottomArea.initWallet(mQuickAccessWalletController);
1176         mKeyguardBottomArea.initControls(mControlsComponent);
1177     }
1178 
updateMaxDisplayedNotifications(boolean recompute)1179     private void updateMaxDisplayedNotifications(boolean recompute) {
1180         if (recompute) {
1181             mMaxAllowedKeyguardNotifications = Math.max(computeMaxKeyguardNotifications(), 1);
1182         }
1183 
1184         if (mKeyguardShowing && !mKeyguardBypassController.getBypassEnabled()) {
1185             mNotificationStackScrollLayoutController.setMaxDisplayedNotifications(
1186                     mMaxAllowedKeyguardNotifications);
1187             mNotificationStackScrollLayoutController.setKeyguardBottomPadding(
1188                     mKeyguardNotificationBottomPadding);
1189         } else {
1190             // no max when not on the keyguard
1191             mNotificationStackScrollLayoutController.setMaxDisplayedNotifications(-1);
1192             mNotificationStackScrollLayoutController.setKeyguardBottomPadding(-1f);
1193         }
1194     }
1195 
setKeyguardIndicationController(KeyguardIndicationController indicationController)1196     public void setKeyguardIndicationController(KeyguardIndicationController indicationController) {
1197         mKeyguardIndicationController = indicationController;
1198         mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
1199     }
1200 
updateGestureExclusionRect()1201     private void updateGestureExclusionRect() {
1202         Rect exclusionRect = calculateGestureExclusionRect();
1203         mView.setSystemGestureExclusionRects(exclusionRect.isEmpty() ? Collections.EMPTY_LIST
1204                 : Collections.singletonList(exclusionRect));
1205     }
1206 
calculateGestureExclusionRect()1207     private Rect calculateGestureExclusionRect() {
1208         Rect exclusionRect = null;
1209         Region touchableRegion = mStatusBarTouchableRegionManager.calculateTouchableRegion();
1210         if (isFullyCollapsed() && touchableRegion != null) {
1211             // Note: The manager also calculates the non-pinned touchable region
1212             exclusionRect = touchableRegion.getBounds();
1213         }
1214         return exclusionRect != null ? exclusionRect : EMPTY_RECT;
1215     }
1216 
setIsFullWidth(boolean isFullWidth)1217     private void setIsFullWidth(boolean isFullWidth) {
1218         mIsFullWidth = isFullWidth;
1219         mNotificationStackScrollLayoutController.setIsFullWidth(isFullWidth);
1220     }
1221 
startQsSizeChangeAnimation(int oldHeight, final int newHeight)1222     private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) {
1223         if (mQsSizeChangeAnimator != null) {
1224             oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
1225             mQsSizeChangeAnimator.cancel();
1226         }
1227         mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight);
1228         mQsSizeChangeAnimator.setDuration(300);
1229         mQsSizeChangeAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
1230         mQsSizeChangeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
1231             @Override
1232             public void onAnimationUpdate(ValueAnimator animation) {
1233                 requestScrollerTopPaddingUpdate(false /* animate */);
1234                 requestPanelHeightUpdate();
1235                 int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
1236                 mQs.setHeightOverride(height);
1237             }
1238         });
1239         mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
1240             @Override
1241             public void onAnimationEnd(Animator animation) {
1242                 mQsSizeChangeAnimator = null;
1243             }
1244         });
1245         mQsSizeChangeAnimator.start();
1246     }
1247 
1248     /**
1249      * Positions the clock and notifications dynamically depending on how many notifications are
1250      * showing.
1251      */
positionClockAndNotifications()1252     private void positionClockAndNotifications() {
1253         positionClockAndNotifications(false /* forceUpdate */);
1254     }
1255 
1256     /**
1257      * Positions the clock and notifications dynamically depending on how many notifications are
1258      * showing.
1259      *
1260      * @param forceClockUpdate Should the clock be updated even when not on keyguard
1261      */
positionClockAndNotifications(boolean forceClockUpdate)1262     private void positionClockAndNotifications(boolean forceClockUpdate) {
1263         boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
1264         int stackScrollerPadding;
1265         boolean onKeyguard = isOnKeyguard();
1266         if (onKeyguard || forceClockUpdate) {
1267             updateClockAppearance();
1268         }
1269         if (!onKeyguard) {
1270             if (mShouldUseSplitNotificationShade) {
1271                 // Quick settings are not on the top of the notifications
1272                 // when in split shade mode (they are on the left side),
1273                 // so we should not add a padding for them
1274                 stackScrollerPadding = 0;
1275             } else {
1276                 stackScrollerPadding = getUnlockedStackScrollerPadding();
1277             }
1278         } else {
1279             stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
1280         }
1281 
1282         mSplitShadeHeaderController.setShadeExpandedFraction(getExpandedFraction());
1283         mNotificationStackScrollLayoutController.setIntrinsicPadding(stackScrollerPadding);
1284         mKeyguardBottomArea.setAntiBurnInOffsetX(mClockPositionResult.clockX);
1285 
1286         mStackScrollerMeasuringPass++;
1287         requestScrollerTopPaddingUpdate(animate);
1288         mStackScrollerMeasuringPass = 0;
1289         mAnimateNextPositionUpdate = false;
1290     }
1291 
updateClockAppearance()1292     private void updateClockAppearance() {
1293         int userSwitcherPreferredY = mStatusBarHeaderHeightKeyguard;
1294         boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
1295         final boolean hasVisibleNotifications = mNotificationStackScrollLayoutController
1296                 .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia();
1297         boolean splitShadeWithActiveMedia =
1298                 mShouldUseSplitNotificationShade && mMediaDataManager.hasActiveMedia();
1299         if ((hasVisibleNotifications && !mShouldUseSplitNotificationShade)
1300                 || (splitShadeWithActiveMedia && !mDozing)) {
1301             mKeyguardStatusViewController.displayClock(SMALL);
1302         } else {
1303             mKeyguardStatusViewController.displayClock(LARGE);
1304         }
1305         updateKeyguardStatusViewAlignment(true /* animate */);
1306         int userSwitcherHeight = mKeyguardQsUserSwitchController != null
1307                 ? mKeyguardQsUserSwitchController.getUserIconHeight() : 0;
1308         if (mKeyguardUserSwitcherController != null) {
1309             userSwitcherHeight = mKeyguardUserSwitcherController.getHeight();
1310         }
1311         float expandedFraction =
1312                 mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
1313                         ? 1.0f : getExpandedFraction();
1314         float darkamount =
1315                 mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
1316                         ? 1.0f : mInterpolatedDarkAmount;
1317 
1318         float udfpsAodTopLocation = -1f;
1319         if (mUpdateMonitor.isUdfpsEnrolled() && mAuthController.getUdfpsProps().size() > 0) {
1320             FingerprintSensorPropertiesInternal props = mAuthController.getUdfpsProps().get(0);
1321             final SensorLocationInternal location = props.getLocation();
1322             udfpsAodTopLocation = location.sensorLocationY - location.sensorRadius
1323                     - mUdfpsMaxYBurnInOffset;
1324         }
1325 
1326         mClockPositionAlgorithm.setup(
1327                 mStatusBarHeaderHeightKeyguard,
1328                 expandedFraction,
1329                 mKeyguardStatusViewController.getLockscreenHeight(),
1330                 userSwitcherHeight,
1331                 userSwitcherPreferredY,
1332                 darkamount, mOverStretchAmount,
1333                 bypassEnabled, getUnlockedStackScrollerPadding(),
1334                 computeQsExpansionFraction(),
1335                 mDisplayTopInset,
1336                 mShouldUseSplitNotificationShade,
1337                 udfpsAodTopLocation,
1338                 mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
1339                 mKeyguardStatusViewController.isClockTopAligned());
1340         mClockPositionAlgorithm.run(mClockPositionResult);
1341         boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
1342         boolean animateClock = animate || mAnimateNextPositionUpdate;
1343         mKeyguardStatusViewController.updatePosition(
1344                 mClockPositionResult.clockX, mClockPositionResult.clockY,
1345                 mClockPositionResult.clockScale, animateClock);
1346         if (mKeyguardQsUserSwitchController != null) {
1347             mKeyguardQsUserSwitchController.updatePosition(
1348                     mClockPositionResult.clockX,
1349                     mClockPositionResult.userSwitchY,
1350                     animateClock);
1351         }
1352         if (mKeyguardUserSwitcherController != null) {
1353             mKeyguardUserSwitcherController.updatePosition(
1354                     mClockPositionResult.clockX,
1355                     mClockPositionResult.userSwitchY,
1356                     animateClock);
1357         }
1358         updateNotificationTranslucency();
1359         updateClock();
1360     }
1361 
updateKeyguardStatusViewAlignment(boolean animate)1362     private void updateKeyguardStatusViewAlignment(boolean animate) {
1363         boolean hasVisibleNotifications = mNotificationStackScrollLayoutController
1364                 .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia();
1365         boolean shouldBeCentered =
1366                 !mShouldUseSplitNotificationShade || !hasVisibleNotifications || mDozing;
1367         if (mStatusViewCentered != shouldBeCentered) {
1368             mStatusViewCentered = shouldBeCentered;
1369             ConstraintSet constraintSet = new ConstraintSet();
1370             constraintSet.clone(mNotificationContainerParent);
1371             int statusConstraint = shouldBeCentered ? PARENT_ID : R.id.qs_edge_guideline;
1372             constraintSet.connect(R.id.keyguard_status_view, END, statusConstraint, END);
1373             if (animate) {
1374                 ChangeBounds transition = new ChangeBounds();
1375                 transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
1376                 transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
1377                 TransitionManager.beginDelayedTransition(mNotificationContainerParent, transition);
1378             }
1379             constraintSet.applyTo(mNotificationContainerParent);
1380         }
1381     }
1382 
1383     /**
1384      * @return the padding of the stackscroller when unlocked
1385      */
getUnlockedStackScrollerPadding()1386     private int getUnlockedStackScrollerPadding() {
1387         return (mQs != null ? mQs.getHeader().getHeight() : 0) + mQsPeekHeight;
1388     }
1389 
1390     /**
1391      * @return the maximum keyguard notifications that can fit on the screen
1392      */
computeMaxKeyguardNotifications()1393     private int computeMaxKeyguardNotifications() {
1394         float minPadding = mClockPositionAlgorithm.getMinStackScrollerPadding();
1395         int notificationPadding = Math.max(
1396                 1, mResources.getDimensionPixelSize(R.dimen.notification_divider_height));
1397         float shelfSize =
1398                 mNotificationShelfController.getVisibility() == View.GONE
1399                         ? 0
1400                         : mNotificationShelfController.getIntrinsicHeight() + notificationPadding;
1401 
1402         float lockIconPadding = 0;
1403         if (mLockIconViewController.getTop() != 0) {
1404             lockIconPadding = mStatusBar.getDisplayHeight() - mLockIconViewController.getTop()
1405                 + mResources.getDimensionPixelSize(R.dimen.min_lock_icon_padding);
1406         }
1407 
1408         float bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
1409         bottomPadding = Math.max(lockIconPadding, bottomPadding);
1410         mKeyguardNotificationBottomPadding = bottomPadding;
1411 
1412         float availableSpace =
1413                 mNotificationStackScrollLayoutController.getHeight()
1414                         - minPadding
1415                         - shelfSize
1416                         - bottomPadding;
1417 
1418         int count = 0;
1419         ExpandableView previousView = null;
1420         for (int i = 0; i < mNotificationStackScrollLayoutController.getChildCount(); i++) {
1421             ExpandableView child = mNotificationStackScrollLayoutController.getChildAt(i);
1422             if (child instanceof ExpandableNotificationRow) {
1423                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1424                 boolean suppressedSummary = mGroupManager != null
1425                         && mGroupManager.isSummaryOfSuppressedGroup(row.getEntry().getSbn());
1426                 if (suppressedSummary) {
1427                     continue;
1428                 }
1429                 if (!canShowViewOnLockscreen(child)) {
1430                     continue;
1431                 }
1432                 if (row.isRemoved()) {
1433                     continue;
1434                 }
1435             } else if (child instanceof MediaHeaderView) {
1436                 if (child.getVisibility() == GONE) {
1437                     continue;
1438                 }
1439                 if (child.getIntrinsicHeight() == 0) {
1440                     continue;
1441                 }
1442             } else {
1443                 continue;
1444             }
1445             availableSpace -= child.getMinHeight(true /* ignoreTemporaryStates */);
1446             availableSpace -= count == 0 ? 0 : notificationPadding;
1447             availableSpace -= mNotificationStackScrollLayoutController
1448                     .calculateGapHeight(previousView, child, count);
1449             previousView = child;
1450             if (availableSpace >= 0
1451                     && (mMaxKeyguardNotifications == -1 || count < mMaxKeyguardNotifications)) {
1452                 count++;
1453             } else if (availableSpace > -shelfSize) {
1454                 // if we are exactly the last view, then we can show us still!
1455                 int childCount = mNotificationStackScrollLayoutController.getChildCount();
1456                 for (int j = i + 1; j < childCount; j++) {
1457                     ExpandableView view = mNotificationStackScrollLayoutController.getChildAt(j);
1458                     if (view instanceof ExpandableNotificationRow
1459                             && canShowViewOnLockscreen(view)) {
1460                         return count;
1461                     }
1462                 }
1463                 count++;
1464                 return count;
1465             } else {
1466                 return count;
1467             }
1468         }
1469         return count;
1470     }
1471 
1472     /**
1473      * Can a view be shown on the lockscreen when calculating the number of allowed notifications
1474      * to show?
1475      *
1476      * @param child the view in question
1477      * @return true if it can be shown
1478      */
canShowViewOnLockscreen(ExpandableView child)1479     private boolean canShowViewOnLockscreen(ExpandableView child) {
1480         if (child.hasNoContentHeight()) {
1481             return false;
1482         }
1483         if (child instanceof ExpandableNotificationRow &&
1484                 !canShowRowOnLockscreen((ExpandableNotificationRow) child)) {
1485             return false;
1486         } else if (child.getVisibility() == GONE) {
1487             // ENRs can be gone and count because their visibility is only set after
1488             // this calculation, but all other views should be up to date
1489             return false;
1490         }
1491         return true;
1492     }
1493 
1494     /**
1495      * Can a row be shown on the lockscreen when calculating the number of allowed notifications
1496      * to show?
1497      *
1498      * @param row the row in question
1499      * @return true if it can be shown
1500      */
canShowRowOnLockscreen(ExpandableNotificationRow row)1501     private boolean canShowRowOnLockscreen(ExpandableNotificationRow row) {
1502         boolean suppressedSummary =
1503                 mGroupManager != null && mGroupManager.isSummaryOfSuppressedGroup(
1504                         row.getEntry().getSbn());
1505         if (suppressedSummary) {
1506             return false;
1507         }
1508         if (!mLockscreenUserManager.shouldShowOnKeyguard(row.getEntry())) {
1509             return false;
1510         }
1511         if (row.isRemoved()) {
1512             return false;
1513         }
1514         return true;
1515     }
1516 
updateClock()1517     private void updateClock() {
1518         float alpha = mClockPositionResult.clockAlpha * mKeyguardOnlyContentAlpha;
1519         mKeyguardStatusViewController.setAlpha(alpha);
1520         if (mKeyguardQsUserSwitchController != null) {
1521             mKeyguardQsUserSwitchController.setAlpha(alpha);
1522         }
1523         if (mKeyguardUserSwitcherController != null) {
1524             mKeyguardUserSwitcherController.setAlpha(alpha);
1525         }
1526     }
1527 
animateToFullShade(long delay)1528     public void animateToFullShade(long delay) {
1529         mNotificationStackScrollLayoutController.goToFullShade(delay);
1530         mView.requestLayout();
1531         mAnimateNextPositionUpdate = true;
1532     }
1533 
setQsExpansionEnabled()1534     private void setQsExpansionEnabled() {
1535         if (mQs == null) return;
1536         mQs.setHeaderClickable(isQsExpansionEnabled());
1537     }
1538 
setQsExpansionEnabledPolicy(boolean qsExpansionEnabledPolicy)1539     public void setQsExpansionEnabledPolicy(boolean qsExpansionEnabledPolicy) {
1540         mQsExpansionEnabledPolicy = qsExpansionEnabledPolicy;
1541         setQsExpansionEnabled();
1542     }
1543 
1544     @Override
resetViews(boolean animate)1545     public void resetViews(boolean animate) {
1546         mIsLaunchTransitionFinished = false;
1547         mBlockTouches = false;
1548         if (!mLaunchingAffordance) {
1549             mAffordanceHelper.reset(false);
1550             mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
1551         }
1552         mStatusBar.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
1553                 true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
1554         if (animate && !isFullyCollapsed()) {
1555             animateCloseQs(true /* animateAway */);
1556         } else {
1557             closeQs();
1558         }
1559         mNotificationStackScrollLayoutController.setOverScrollAmount(0f, true /* onTop */, animate,
1560                 !animate /* cancelAnimators */);
1561         mNotificationStackScrollLayoutController.resetScrollPosition();
1562     }
1563 
1564     /** Collapses the panel. */
collapsePanel(boolean animate, boolean delayed, float speedUpFactor)1565     public void collapsePanel(boolean animate, boolean delayed, float speedUpFactor) {
1566         boolean waiting = false;
1567         if (animate && !isFullyCollapsed()) {
1568             collapse(delayed, speedUpFactor);
1569             waiting = true;
1570         } else {
1571             resetViews(false /* animate */);
1572             setExpandedFraction(0); // just in case
1573         }
1574         if (!waiting) {
1575             // it's possible that nothing animated, so we replicate the termination
1576             // conditions of panelExpansionChanged here
1577             // TODO(b/200063118): This can likely go away in a future refactor CL.
1578             getPanelExpansionStateManager().updateState(STATE_CLOSED);
1579         }
1580     }
1581 
1582     @Override
collapse(boolean delayed, float speedUpFactor)1583     public void collapse(boolean delayed, float speedUpFactor) {
1584         if (!canPanelBeCollapsed()) {
1585             return;
1586         }
1587 
1588         if (mQsExpanded) {
1589             mQsExpandImmediate = true;
1590             mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
1591         }
1592         super.collapse(delayed, speedUpFactor);
1593     }
1594 
closeQs()1595     public void closeQs() {
1596         cancelQsAnimation();
1597         setQsExpansion(mQsMinExpansionHeight);
1598     }
1599 
cancelAnimation()1600     public void cancelAnimation() {
1601         mView.animate().cancel();
1602     }
1603 
1604 
1605     /**
1606      * Animate QS closing by flinging it.
1607      * If QS is expanded, it will collapse into QQS and stop.
1608      *
1609      * @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
1610      */
animateCloseQs(boolean animateAway)1611     public void animateCloseQs(boolean animateAway) {
1612         if (mQsExpansionAnimator != null) {
1613             if (!mQsAnimatorExpand) {
1614                 return;
1615             }
1616             float height = mQsExpansionHeight;
1617             mQsExpansionAnimator.cancel();
1618             setQsExpansion(height);
1619         }
1620         flingSettings(0 /* vel */, animateAway ? FLING_HIDE : FLING_COLLAPSE);
1621     }
1622 
isQsExpansionEnabled()1623     private boolean isQsExpansionEnabled() {
1624         return mQsExpansionEnabledPolicy && mQsExpansionEnabledAmbient
1625                 && !mRemoteInputManager.isRemoteInputActive();
1626     }
1627 
expandWithQs()1628     public void expandWithQs() {
1629         if (isQsExpansionEnabled()) {
1630             mQsExpandImmediate = true;
1631             mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
1632         }
1633         if (isFullyCollapsed()) {
1634             expand(true /* animate */);
1635         } else {
1636             traceQsJank(true /* startTracing */, false /* wasCancelled */);
1637             flingSettings(0 /* velocity */, FLING_EXPAND);
1638         }
1639     }
1640 
expandWithQsDetail(DetailAdapter qsDetailAdapter)1641     public void expandWithQsDetail(DetailAdapter qsDetailAdapter) {
1642         traceQsJank(true /* startTracing */, false /* wasCancelled */);
1643         flingSettings(0 /* velocity */, FLING_EXPAND);
1644         // When expanding with a panel, there's no meaningful touch point to correspond to. Set the
1645         // origin to somewhere above the screen. This is used for animations.
1646         int x = mQsFrame.getWidth() / 2;
1647         mQSDetailDisplayer.showDetailAdapter(qsDetailAdapter, x, -getHeight());
1648         if (mAccessibilityManager.isEnabled()) {
1649             mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
1650         }
1651     }
1652 
expandWithoutQs()1653     public void expandWithoutQs() {
1654         if (isQsExpanded()) {
1655             flingSettings(0 /* velocity */, FLING_COLLAPSE);
1656         } else {
1657             expand(true /* animate */);
1658         }
1659     }
1660 
1661     @Override
fling(float vel, boolean expand)1662     public void fling(float vel, boolean expand) {
1663         GestureRecorder gr = mStatusBar.getGestureRecorder();
1664         if (gr != null) {
1665             gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
1666         }
1667         super.fling(vel, expand);
1668     }
1669 
1670     @Override
flingToHeight(float vel, boolean expand, float target, float collapseSpeedUpFactor, boolean expandBecauseOfFalsing)1671     protected void flingToHeight(float vel, boolean expand, float target,
1672             float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
1673         mHeadsUpTouchHelper.notifyFling(!expand);
1674         mKeyguardStateController.notifyPanelFlingStart(!expand /* flingingToDismiss */);
1675         setClosingWithAlphaFadeout(!expand && !isOnKeyguard() && getFadeoutAlpha() == 1.0f);
1676         super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
1677     }
1678 
onQsIntercept(MotionEvent event)1679     private boolean onQsIntercept(MotionEvent event) {
1680         int pointerIndex = event.findPointerIndex(mTrackingPointer);
1681         if (pointerIndex < 0) {
1682             pointerIndex = 0;
1683             mTrackingPointer = event.getPointerId(pointerIndex);
1684         }
1685         final float x = event.getX(pointerIndex);
1686         final float y = event.getY(pointerIndex);
1687 
1688         switch (event.getActionMasked()) {
1689             case MotionEvent.ACTION_DOWN:
1690                 mInitialTouchY = y;
1691                 mInitialTouchX = x;
1692                 initVelocityTracker();
1693                 trackMovement(event);
1694                 if (mKeyguardShowing
1695                         && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
1696                     // Dragging down on the lockscreen statusbar should prohibit other interactions
1697                     // immediately, otherwise we'll wait on the touchslop. This is to allow
1698                     // dragging down to expanded quick settings directly on the lockscreen.
1699                     mView.getParent().requestDisallowInterceptTouchEvent(true);
1700                 }
1701                 if (mQsExpansionAnimator != null) {
1702                     mInitialHeightOnTouch = mQsExpansionHeight;
1703                     mQsTracking = true;
1704                     traceQsJank(true /* startTracing */, false /* wasCancelled */);
1705                     mNotificationStackScrollLayoutController.cancelLongPress();
1706                 }
1707                 break;
1708             case MotionEvent.ACTION_POINTER_UP:
1709                 final int upPointer = event.getPointerId(event.getActionIndex());
1710                 if (mTrackingPointer == upPointer) {
1711                     // gesture is ongoing, find a new pointer to track
1712                     final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
1713                     mTrackingPointer = event.getPointerId(newIndex);
1714                     mInitialTouchX = event.getX(newIndex);
1715                     mInitialTouchY = event.getY(newIndex);
1716                 }
1717                 break;
1718 
1719             case MotionEvent.ACTION_MOVE:
1720                 final float h = y - mInitialTouchY;
1721                 trackMovement(event);
1722                 if (mQsTracking) {
1723 
1724                     // Already tracking because onOverscrolled was called. We need to update here
1725                     // so we don't stop for a frame until the next touch event gets handled in
1726                     // onTouchEvent.
1727                     setQsExpansion(h + mInitialHeightOnTouch);
1728                     trackMovement(event);
1729                     return true;
1730                 }
1731                 if ((h > getTouchSlop(event) || (h < -getTouchSlop(event) && mQsExpanded))
1732                         && Math.abs(h) > Math.abs(x - mInitialTouchX)
1733                         && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
1734                     mView.getParent().requestDisallowInterceptTouchEvent(true);
1735                     mQsTracking = true;
1736                     traceQsJank(true /* startTracing */, false /* wasCancelled */);
1737                     onQsExpansionStarted();
1738                     notifyExpandingFinished();
1739                     mInitialHeightOnTouch = mQsExpansionHeight;
1740                     mInitialTouchY = y;
1741                     mInitialTouchX = x;
1742                     mNotificationStackScrollLayoutController.cancelLongPress();
1743                     return true;
1744                 }
1745                 break;
1746 
1747             case MotionEvent.ACTION_CANCEL:
1748             case MotionEvent.ACTION_UP:
1749                 trackMovement(event);
1750                 mQsTracking = false;
1751                 break;
1752         }
1753         return false;
1754     }
1755 
1756     @Override
isInContentBounds(float x, float y)1757     protected boolean isInContentBounds(float x, float y) {
1758         float stackScrollerX = mNotificationStackScrollLayoutController.getX();
1759         return !mNotificationStackScrollLayoutController
1760                 .isBelowLastNotification(x - stackScrollerX, y)
1761                 && stackScrollerX < x
1762                 && x < stackScrollerX + mNotificationStackScrollLayoutController.getWidth();
1763     }
1764 
traceQsJank(boolean startTracing, boolean wasCancelled)1765     private void traceQsJank(boolean startTracing, boolean wasCancelled) {
1766         InteractionJankMonitor monitor = InteractionJankMonitor.getInstance();
1767         if (startTracing) {
1768             monitor.begin(mView, CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
1769         } else {
1770             if (wasCancelled) {
1771                 monitor.cancel(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
1772             } else {
1773                 monitor.end(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
1774             }
1775         }
1776     }
1777 
initDownStates(MotionEvent event)1778     private void initDownStates(MotionEvent event) {
1779         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
1780             mOnlyAffordanceInThisMotion = false;
1781             mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
1782             mDozingOnDown = isDozing();
1783             mDownX = event.getX();
1784             mDownY = event.getY();
1785             mCollapsedOnDown = isFullyCollapsed();
1786             mIsPanelCollapseOnQQS = canPanelCollapseOnQQS(mDownX, mDownY);
1787             mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp();
1788             mAllowExpandForSmallExpansion = mExpectingSynthesizedDown;
1789             mTouchSlopExceededBeforeDown = mExpectingSynthesizedDown;
1790             if (mExpectingSynthesizedDown) {
1791                 mLastEventSynthesizedDown = true;
1792             } else {
1793                 // down but not synthesized motion event.
1794                 mLastEventSynthesizedDown = false;
1795             }
1796         } else {
1797             // not down event at all.
1798             mLastEventSynthesizedDown = false;
1799         }
1800     }
1801 
1802     /**
1803      * Can the panel collapse in this motion because it was started on QQS?
1804      *
1805      * @param downX the x location where the touch started
1806      * @param downY the y location where the touch started
1807      *
1808      * @return true if the panel could be collapsed because it stared on QQS
1809      */
canPanelCollapseOnQQS(float downX, float downY)1810     private boolean canPanelCollapseOnQQS(float downX, float downY) {
1811         if (mCollapsedOnDown || mKeyguardShowing || mQsExpanded) {
1812             return false;
1813         }
1814         View header = mQs == null ? mKeyguardStatusBar : mQs.getHeader();
1815         return downX >= mQsFrame.getX() && downX <= mQsFrame.getX() + mQsFrame.getWidth()
1816                         && downY <= header.getBottom();
1817 
1818     }
1819 
flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent)1820     private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) {
1821         float vel = getCurrentQSVelocity();
1822         boolean expandsQs = flingExpandsQs(vel);
1823         if (expandsQs) {
1824             if (mFalsingManager.isUnlockingDisabled() || isFalseTouch(QUICK_SETTINGS)) {
1825                 expandsQs = false;
1826             } else {
1827                 logQsSwipeDown(y);
1828             }
1829         } else if (vel < 0) {
1830             mFalsingManager.isFalseTouch(QS_COLLAPSE);
1831         }
1832 
1833         flingSettings(vel, expandsQs && !isCancelMotionEvent ? FLING_EXPAND : FLING_COLLAPSE);
1834     }
1835 
logQsSwipeDown(float y)1836     private void logQsSwipeDown(float y) {
1837         float vel = getCurrentQSVelocity();
1838         final int
1839                 gesture =
1840                 mBarState == KEYGUARD ? MetricsEvent.ACTION_LS_QS
1841                         : MetricsEvent.ACTION_SHADE_QS_PULL;
1842         mLockscreenGestureLogger.write(gesture,
1843                 (int) ((y - mInitialTouchY) / mStatusBar.getDisplayDensity()),
1844                 (int) (vel / mStatusBar.getDisplayDensity()));
1845     }
1846 
flingExpandsQs(float vel)1847     private boolean flingExpandsQs(float vel) {
1848         if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
1849             return computeQsExpansionFraction() > 0.5f;
1850         } else {
1851             return vel > 0;
1852         }
1853     }
1854 
isFalseTouch(@lassifier.InteractionType int interactionType)1855     private boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
1856         if (mFalsingManager.isClassifierEnabled()) {
1857             return mFalsingManager.isFalseTouch(interactionType);
1858         }
1859         return !mQsTouchAboveFalsingThreshold;
1860     }
1861 
computeQsExpansionFraction()1862     private float computeQsExpansionFraction() {
1863         if (mQSAnimatingHiddenFromCollapsed) {
1864             // When hiding QS from collapsed state, the expansion can sometimes temporarily
1865             // be larger than 0 because of the timing, leading to flickers.
1866             return 0.0f;
1867         }
1868         return Math.min(
1869                 1f, (mQsExpansionHeight - mQsMinExpansionHeight) / (mQsMaxExpansionHeight
1870                         - mQsMinExpansionHeight));
1871     }
1872 
1873     @Override
shouldExpandWhenNotFlinging()1874     protected boolean shouldExpandWhenNotFlinging() {
1875         if (super.shouldExpandWhenNotFlinging()) {
1876             return true;
1877         }
1878         if (mAllowExpandForSmallExpansion) {
1879             // When we get a touch that came over from launcher, the velocity isn't always correct
1880             // Let's err on expanding if the gesture has been reasonably slow
1881             long timeSinceDown = SystemClock.uptimeMillis() - mDownTime;
1882             return timeSinceDown <= MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER;
1883         }
1884         return false;
1885     }
1886 
1887     @Override
getOpeningHeight()1888     protected float getOpeningHeight() {
1889         return mNotificationStackScrollLayoutController.getOpeningHeight();
1890     }
1891 
1892 
handleQsTouch(MotionEvent event)1893     private boolean handleQsTouch(MotionEvent event) {
1894         if (mShouldUseSplitNotificationShade && touchXOutsideOfQs(event.getX())) {
1895             return false;
1896         }
1897         final int action = event.getActionMasked();
1898         if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
1899                 && mBarState != KEYGUARD && !mQsExpanded && isQsExpansionEnabled()) {
1900             // Down in the empty area while fully expanded - go to QS.
1901             mQsTracking = true;
1902             traceQsJank(true /* startTracing */, false /* wasCancelled */);
1903             mConflictingQsExpansionGesture = true;
1904             onQsExpansionStarted();
1905             mInitialHeightOnTouch = mQsExpansionHeight;
1906             mInitialTouchY = event.getX();
1907             mInitialTouchX = event.getY();
1908         }
1909         if (!isFullyCollapsed()) {
1910             handleQsDown(event);
1911         }
1912         if (!mQsExpandImmediate && mQsTracking) {
1913             onQsTouch(event);
1914             if (!mConflictingQsExpansionGesture) {
1915                 return true;
1916             }
1917         }
1918         if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
1919             mConflictingQsExpansionGesture = false;
1920         }
1921         if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed() && isQsExpansionEnabled()) {
1922             mTwoFingerQsExpandPossible = true;
1923         }
1924         if (mTwoFingerQsExpandPossible && isOpenQsEvent(event) && event.getY(event.getActionIndex())
1925                 < mStatusBarMinHeight) {
1926             mMetricsLogger.count(COUNTER_PANEL_OPEN_QS, 1);
1927             mQsExpandImmediate = true;
1928             mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
1929             requestPanelHeightUpdate();
1930 
1931             // Normally, we start listening when the panel is expanded, but here we need to start
1932             // earlier so the state is already up to date when dragging down.
1933             setListening(true);
1934         }
1935         return false;
1936     }
1937 
touchXOutsideOfQs(float touchX)1938     private boolean touchXOutsideOfQs(float touchX) {
1939         return touchX < mQsFrame.getX() || touchX > mQsFrame.getX() + mQsFrame.getWidth();
1940     }
1941 
isInQsArea(float x, float y)1942     private boolean isInQsArea(float x, float y) {
1943         if (touchXOutsideOfQs(x)) {
1944             return false;
1945         }
1946         // Let's reject anything at the very bottom around the home handle in gesture nav
1947         if (mIsGestureNavigation && y > mView.getHeight() - mNavigationBarBottomHeight) {
1948             return false;
1949         }
1950         return y <= mNotificationStackScrollLayoutController.getBottomMostNotificationBottom()
1951                         || y <= mQs.getView().getY() + mQs.getView().getHeight();
1952     }
1953 
isOpenQsEvent(MotionEvent event)1954     private boolean isOpenQsEvent(MotionEvent event) {
1955         final int pointerCount = event.getPointerCount();
1956         final int action = event.getActionMasked();
1957 
1958         final boolean
1959                 twoFingerDrag =
1960                 action == MotionEvent.ACTION_POINTER_DOWN && pointerCount == 2;
1961 
1962         final boolean
1963                 stylusButtonClickDrag =
1964                 action == MotionEvent.ACTION_DOWN && (event.isButtonPressed(
1965                         MotionEvent.BUTTON_STYLUS_PRIMARY) || event.isButtonPressed(
1966                         MotionEvent.BUTTON_STYLUS_SECONDARY));
1967 
1968         final boolean
1969                 mouseButtonClickDrag =
1970                 action == MotionEvent.ACTION_DOWN && (event.isButtonPressed(
1971                         MotionEvent.BUTTON_SECONDARY) || event.isButtonPressed(
1972                         MotionEvent.BUTTON_TERTIARY));
1973 
1974         return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag;
1975     }
1976 
handleQsDown(MotionEvent event)1977     private void handleQsDown(MotionEvent event) {
1978         if (event.getActionMasked() == MotionEvent.ACTION_DOWN && shouldQuickSettingsIntercept(
1979                 event.getX(), event.getY(), -1)) {
1980             mFalsingCollector.onQsDown();
1981             mQsTracking = true;
1982             onQsExpansionStarted();
1983             mInitialHeightOnTouch = mQsExpansionHeight;
1984             mInitialTouchY = event.getX();
1985             mInitialTouchX = event.getY();
1986 
1987             // If we interrupt an expansion gesture here, make sure to update the state correctly.
1988             notifyExpandingFinished();
1989         }
1990     }
1991 
1992     /**
1993      * Input focus transfer is about to happen.
1994      */
startWaitingForOpenPanelGesture()1995     public void startWaitingForOpenPanelGesture() {
1996         if (!isFullyCollapsed()) {
1997             return;
1998         }
1999         mExpectingSynthesizedDown = true;
2000         onTrackingStarted();
2001         updatePanelExpanded();
2002     }
2003 
2004     /**
2005      * Called when this view is no longer waiting for input focus transfer.
2006      *
2007      * There are two scenarios behind this function call. First, input focus transfer
2008      * has successfully happened and this view already received synthetic DOWN event.
2009      * (mExpectingSynthesizedDown == false). Do nothing.
2010      *
2011      * Second, before input focus transfer finished, user may have lifted finger
2012      * in previous window and this window never received synthetic DOWN event.
2013      * (mExpectingSynthesizedDown == true).
2014      * In this case, we use the velocity to trigger fling event.
2015      *
2016      * @param velocity unit is in px / millis
2017      */
stopWaitingForOpenPanelGesture(boolean cancel, final float velocity)2018     public void stopWaitingForOpenPanelGesture(boolean cancel, final float velocity) {
2019         if (mExpectingSynthesizedDown) {
2020             mExpectingSynthesizedDown = false;
2021             if (cancel) {
2022                 collapse(false /* delayed */, 1.0f /* speedUpFactor */);
2023             } else {
2024                 maybeVibrateOnOpening();
2025                 fling(velocity > 1f ? 1000f * velocity : 0, true /* expand */);
2026             }
2027             onTrackingStopped(false);
2028         }
2029     }
2030 
2031     @Override
flingExpands(float vel, float vectorVel, float x, float y)2032     protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
2033         boolean expands = super.flingExpands(vel, vectorVel, x, y);
2034 
2035         // If we are already running a QS expansion, make sure that we keep the panel open.
2036         if (mQsExpansionAnimator != null) {
2037             expands = true;
2038         }
2039         return expands;
2040     }
2041 
2042     @Override
shouldGestureWaitForTouchSlop()2043     protected boolean shouldGestureWaitForTouchSlop() {
2044         if (mExpectingSynthesizedDown) {
2045             mExpectingSynthesizedDown = false;
2046             return false;
2047         }
2048         return isFullyCollapsed() || mBarState != StatusBarState.SHADE;
2049     }
2050 
2051     @Override
shouldGestureIgnoreXTouchSlop(float x, float y)2052     protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) {
2053         return !mAffordanceHelper.isOnAffordanceIcon(x, y);
2054     }
2055 
onQsTouch(MotionEvent event)2056     private void onQsTouch(MotionEvent event) {
2057         int pointerIndex = event.findPointerIndex(mTrackingPointer);
2058         if (pointerIndex < 0) {
2059             pointerIndex = 0;
2060             mTrackingPointer = event.getPointerId(pointerIndex);
2061         }
2062         final float y = event.getY(pointerIndex);
2063         final float x = event.getX(pointerIndex);
2064         final float h = y - mInitialTouchY;
2065 
2066         switch (event.getActionMasked()) {
2067             case MotionEvent.ACTION_DOWN:
2068                 mQsTracking = true;
2069                 traceQsJank(true /* startTracing */, false /* wasCancelled */);
2070                 mInitialTouchY = y;
2071                 mInitialTouchX = x;
2072                 onQsExpansionStarted();
2073                 mInitialHeightOnTouch = mQsExpansionHeight;
2074                 initVelocityTracker();
2075                 trackMovement(event);
2076                 break;
2077 
2078             case MotionEvent.ACTION_POINTER_UP:
2079                 final int upPointer = event.getPointerId(event.getActionIndex());
2080                 if (mTrackingPointer == upPointer) {
2081                     // gesture is ongoing, find a new pointer to track
2082                     final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
2083                     final float newY = event.getY(newIndex);
2084                     final float newX = event.getX(newIndex);
2085                     mTrackingPointer = event.getPointerId(newIndex);
2086                     mInitialHeightOnTouch = mQsExpansionHeight;
2087                     mInitialTouchY = newY;
2088                     mInitialTouchX = newX;
2089                 }
2090                 break;
2091 
2092             case MotionEvent.ACTION_MOVE:
2093                 setQsExpansion(h + mInitialHeightOnTouch);
2094                 if (h >= getFalsingThreshold()) {
2095                     mQsTouchAboveFalsingThreshold = true;
2096                 }
2097                 trackMovement(event);
2098                 break;
2099 
2100             case MotionEvent.ACTION_UP:
2101             case MotionEvent.ACTION_CANCEL:
2102                 mQsTracking = false;
2103                 mTrackingPointer = -1;
2104                 trackMovement(event);
2105                 float fraction = computeQsExpansionFraction();
2106                 if (fraction != 0f || y >= mInitialTouchY) {
2107                     flingQsWithCurrentVelocity(y,
2108                             event.getActionMasked() == MotionEvent.ACTION_CANCEL);
2109                 } else {
2110                     traceQsJank(false /* startTracing */,
2111                             event.getActionMasked() == MotionEvent.ACTION_CANCEL);
2112                 }
2113                 if (mQsVelocityTracker != null) {
2114                     mQsVelocityTracker.recycle();
2115                     mQsVelocityTracker = null;
2116                 }
2117                 break;
2118         }
2119     }
2120 
getFalsingThreshold()2121     private int getFalsingThreshold() {
2122         float factor = mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
2123         return (int) (mQsFalsingThreshold * factor);
2124     }
2125 
setOverScrolling(boolean overscrolling)2126     private void setOverScrolling(boolean overscrolling) {
2127         mStackScrollerOverscrolling = overscrolling;
2128         if (mQs == null) return;
2129         mQs.setOverscrolling(overscrolling);
2130     }
2131 
onQsExpansionStarted()2132     private void onQsExpansionStarted() {
2133         onQsExpansionStarted(0);
2134     }
2135 
onQsExpansionStarted(int overscrollAmount)2136     protected void onQsExpansionStarted(int overscrollAmount) {
2137         cancelQsAnimation();
2138         cancelHeightAnimator();
2139 
2140         // Reset scroll position and apply that position to the expanded height.
2141         float height = mQsExpansionHeight - overscrollAmount;
2142         setQsExpansion(height);
2143         requestPanelHeightUpdate();
2144         mNotificationStackScrollLayoutController.checkSnoozeLeavebehind();
2145 
2146         // When expanding QS, let's authenticate the user if possible,
2147         // this will speed up notification actions.
2148         if (height == 0) {
2149             mStatusBar.requestFaceAuth(false);
2150         }
2151     }
2152 
setQsExpanded(boolean expanded)2153     @VisibleForTesting void setQsExpanded(boolean expanded) {
2154         boolean changed = mQsExpanded != expanded;
2155         if (changed) {
2156             mQsExpanded = expanded;
2157             updateQsState();
2158             requestPanelHeightUpdate();
2159             mFalsingCollector.setQsExpanded(expanded);
2160             mStatusBar.setQsExpanded(expanded);
2161             mNotificationsQSContainerController.setQsExpanded(expanded);
2162             mPulseExpansionHandler.setQsExpanded(expanded);
2163             mKeyguardBypassController.setQSExpanded(expanded);
2164             mStatusBarKeyguardViewManager.setQsExpanded(expanded);
2165             mLockIconViewController.setQsExpanded(expanded);
2166             mPrivacyDotViewController.setQsExpanded(expanded);
2167         }
2168     }
2169 
maybeAnimateBottomAreaAlpha()2170     private void maybeAnimateBottomAreaAlpha() {
2171         mBottomAreaShadeAlphaAnimator.cancel();
2172         if (mBarState == StatusBarState.SHADE_LOCKED) {
2173             mBottomAreaShadeAlphaAnimator.setFloatValues(mBottomAreaShadeAlpha, 0.0f);
2174             mBottomAreaShadeAlphaAnimator.start();
2175         } else {
2176             mBottomAreaShadeAlpha = 1f;
2177         }
2178     }
2179 
2180     private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() {
2181         @Override
2182         public void run() {
2183             mKeyguardBottomArea.setVisibility(View.GONE);
2184         }
2185     };
2186 
setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade)2187     private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) {
2188         mKeyguardBottomArea.animate().cancel();
2189         if (goingToFullShade) {
2190             mKeyguardBottomArea.animate().alpha(0f).setStartDelay(
2191                     mKeyguardStateController.getKeyguardFadingAwayDelay()).setDuration(
2192                     mKeyguardStateController.getShortenedFadingAwayDuration()).setInterpolator(
2193                     Interpolators.ALPHA_OUT).withEndAction(
2194                     mAnimateKeyguardBottomAreaInvisibleEndRunnable).start();
2195         } else if (statusBarState == KEYGUARD
2196                 || statusBarState == StatusBarState.SHADE_LOCKED) {
2197             mKeyguardBottomArea.setVisibility(View.VISIBLE);
2198             mKeyguardBottomArea.setAlpha(1f);
2199         } else {
2200             mKeyguardBottomArea.setVisibility(View.GONE);
2201         }
2202     }
2203 
updateQsState()2204     private void updateQsState() {
2205         mNotificationStackScrollLayoutController.setQsExpanded(mQsExpanded);
2206         mNotificationStackScrollLayoutController.setScrollingEnabled(
2207                 mBarState != KEYGUARD
2208                         && (!mQsExpanded
2209                             || mQsExpansionFromOverscroll
2210                             || mShouldUseSplitNotificationShade));
2211 
2212         if (mKeyguardUserSwitcherController != null && mQsExpanded
2213                 && !mStackScrollerOverscrolling) {
2214             mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(true);
2215         }
2216         if (mQs == null) return;
2217         mQs.setExpanded(mQsExpanded);
2218     }
2219 
setQsExpansion(float height)2220     void setQsExpansion(float height) {
2221         height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
2222         mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0;
2223         if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling
2224                 && !mDozing) {
2225             setQsExpanded(true);
2226         } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
2227             setQsExpanded(false);
2228         }
2229         mQsExpansionHeight = height;
2230         updateQsExpansion();
2231         requestScrollerTopPaddingUpdate(false /* animate */);
2232         mKeyguardStatusBarViewController.updateViewState();
2233         if (mBarState == StatusBarState.SHADE_LOCKED || mBarState == KEYGUARD) {
2234             updateKeyguardBottomAreaAlpha();
2235             positionClockAndNotifications();
2236         }
2237 
2238         if (mAccessibilityManager.isEnabled()) {
2239             mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
2240         }
2241 
2242         if (!mFalsingManager.isUnlockingDisabled() && mQsFullyExpanded
2243                 && mFalsingCollector.shouldEnforceBouncer()) {
2244             mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
2245                     false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
2246         }
2247         if (DEBUG) {
2248             mView.invalidate();
2249         }
2250     }
2251 
updateQsExpansion()2252     private void updateQsExpansion() {
2253         if (mQs == null) return;
2254         final float squishiness =
2255                 mQsExpandImmediate || mQsExpanded ? 1f : mNotificationStackScrollLayoutController
2256                         .getNotificationSquishinessFraction();
2257         final float qsExpansionFraction = computeQsExpansionFraction();
2258         final float adjustedExpansionFraction = mShouldUseSplitNotificationShade
2259                 ? 1f : computeQsExpansionFraction();
2260         mQs.setQsExpansion(adjustedExpansionFraction, getExpandedFraction(), getHeaderTranslation(),
2261                 squishiness);
2262         mSplitShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
2263         mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
2264         int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction);
2265         mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
2266         setQSClippingBounds();
2267 
2268         // Only need to notify the notification stack when we're not in split screen mode. If we
2269         // do, then the notification panel starts scrolling along with the QS.
2270         if (!mShouldUseSplitNotificationShade) {
2271             mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction);
2272         }
2273 
2274         mDepthController.setQsPanelExpansion(qsExpansionFraction);
2275     }
2276 
onStackYChanged(boolean shouldAnimate)2277     private void onStackYChanged(boolean shouldAnimate) {
2278         if (mQs != null) {
2279             if (shouldAnimate) {
2280                 animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_STANDARD,
2281                         0 /* delay */);
2282                 mNotificationBoundsAnimationDelay = 0;
2283             }
2284             setQSClippingBounds();
2285         }
2286     };
2287 
onNotificationScrolled(int newScrollPosition)2288     private void onNotificationScrolled(int newScrollPosition) {
2289         updateQSExpansionEnabledAmbient();
2290     }
2291 
updateQSExpansionEnabledAmbient()2292     private void updateQSExpansionEnabledAmbient() {
2293         final float scrollRangeToTop = mAmbientState.getTopPadding() - mQuickQsOffsetHeight;
2294         mQsExpansionEnabledAmbient = mShouldUseSplitNotificationShade
2295                 || (mAmbientState.getScrollY() <= scrollRangeToTop);
2296         setQsExpansionEnabled();
2297     }
2298 
2299     /**
2300      * Updates scrim bounds, QS clipping, and KSV clipping as well based on the bounds of the shade
2301      * and QS state.
2302      */
setQSClippingBounds()2303     private void setQSClippingBounds() {
2304         int top;
2305         int bottom;
2306         int left;
2307         int right;
2308 
2309         final int qsPanelBottomY = calculateQsBottomPosition(computeQsExpansionFraction());
2310         final boolean qsVisible = (computeQsExpansionFraction() > 0 || qsPanelBottomY > 0);
2311 
2312         if (!mShouldUseSplitNotificationShade) {
2313             if (mTransitioningToFullShadeProgress > 0.0f) {
2314                 // If we're transitioning, let's use the actual value. The else case
2315                 // can be wrong during transitions when waiting for the keyguard to unlock
2316                 top = mTransitionToFullShadeQSPosition;
2317             } else {
2318                 final float notificationTop = getQSEdgePosition();
2319                 if (isOnKeyguard()) {
2320                     if (mKeyguardBypassController.getBypassEnabled()) {
2321                         // When bypassing on the keyguard, let's use the panel bottom.
2322                         // this should go away once we unify the stackY position and don't have
2323                         // to do this min anymore below.
2324                         top = qsPanelBottomY;
2325                     } else {
2326                         top = (int) Math.min(qsPanelBottomY, notificationTop);
2327                     }
2328                 } else {
2329                     top = (int) notificationTop;
2330                 }
2331             }
2332             top += mOverStretchAmount;
2333             // Correction for instant expansion caused by HUN pull down/
2334             if (mMinFraction > 0f && mMinFraction < 1f) {
2335                 float realFraction =
2336                         (getExpandedFraction() - mMinFraction) / (1f - mMinFraction);
2337                 top *= MathUtils.saturate(realFraction / mMinFraction);
2338             }
2339             bottom = getView().getBottom();
2340             // notification bounds should take full screen width regardless of insets
2341             left = 0;
2342             right = getView().getRight() + mDisplayRightInset;
2343         } else {
2344             top = Math.min(qsPanelBottomY, mSplitShadeStatusBarHeight);
2345             bottom = top + mNotificationStackScrollLayoutController.getHeight();
2346             left = mNotificationStackScrollLayoutController.getLeft();
2347             right = mNotificationStackScrollLayoutController.getRight();
2348         }
2349         // top should never be lower than bottom, otherwise it will be invisible.
2350         top = Math.min(top, bottom);
2351         applyQSClippingBounds(left, top, right, bottom, qsVisible);
2352     }
2353 
applyQSClippingBounds(int left, int top, int right, int bottom, boolean qsVisible)2354     private void applyQSClippingBounds(int left, int top, int right, int bottom,
2355             boolean qsVisible) {
2356         if (!mAnimateNextNotificationBounds || mKeyguardStatusAreaClipBounds.isEmpty()) {
2357             if (mQsClippingAnimation != null) {
2358                 // update the end position of the animator
2359                 mQsClippingAnimationEndBounds.set(left, top, right, bottom);
2360             } else {
2361                 applyQSClippingImmediately(left, top, right, bottom, qsVisible);
2362             }
2363         } else {
2364             mQsClippingAnimationEndBounds.set(left, top, right, bottom);
2365             final int startLeft = mKeyguardStatusAreaClipBounds.left;
2366             final int startTop = mKeyguardStatusAreaClipBounds.top;
2367             final int startRight = mKeyguardStatusAreaClipBounds.right;
2368             final int startBottom = mKeyguardStatusAreaClipBounds.bottom;
2369             if (mQsClippingAnimation != null) {
2370                 mQsClippingAnimation.cancel();
2371             }
2372             mQsClippingAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
2373             mQsClippingAnimation.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
2374             mQsClippingAnimation.setDuration(mNotificationBoundsAnimationDuration);
2375             mQsClippingAnimation.setStartDelay(mNotificationBoundsAnimationDelay);
2376             mQsClippingAnimation.addUpdateListener(animation -> {
2377                 float fraction = animation.getAnimatedFraction();
2378                 int animLeft = (int) MathUtils.lerp(startLeft,
2379                         mQsClippingAnimationEndBounds.left, fraction);
2380                 int animTop = (int) MathUtils.lerp(startTop,
2381                         mQsClippingAnimationEndBounds.top, fraction);
2382                 int animRight = (int) MathUtils.lerp(startRight,
2383                         mQsClippingAnimationEndBounds.right, fraction);
2384                 int animBottom = (int) MathUtils.lerp(startBottom,
2385                         mQsClippingAnimationEndBounds.bottom, fraction);
2386                 applyQSClippingImmediately(animLeft, animTop, animRight, animBottom,
2387                         qsVisible /* qsVisible */);
2388             });
2389             mQsClippingAnimation.addListener(new AnimatorListenerAdapter() {
2390                 @Override
2391                 public void onAnimationEnd(Animator animation) {
2392                     mQsClippingAnimation = null;
2393                     mIsQsTranslationResetAnimator = false;
2394                     mIsPulseExpansionResetAnimator = false;
2395                 }
2396             });
2397             mQsClippingAnimation.start();
2398         }
2399         mAnimateNextNotificationBounds = false;
2400         mNotificationBoundsAnimationDelay = 0;
2401     }
2402 
applyQSClippingImmediately(int left, int top, int right, int bottom, boolean qsVisible)2403     private void applyQSClippingImmediately(int left, int top, int right, int bottom,
2404             boolean qsVisible) {
2405         // Fancy clipping for quick settings
2406         int radius = mScrimCornerRadius;
2407         boolean clipStatusView = false;
2408         if (!mShouldUseSplitNotificationShade) {
2409             // The padding on this area is large enough that we can use a cheaper clipping strategy
2410             mKeyguardStatusAreaClipBounds.set(left, top, right, bottom);
2411             clipStatusView = qsVisible;
2412             float screenCornerRadius = mRecordingController.isRecording() ? 0 : mScreenCornerRadius;
2413             radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
2414                     Math.min(top / (float) mScrimCornerRadius, 1f));
2415         }
2416         if (mQs != null) {
2417             float qsTranslation = 0;
2418             boolean pulseExpanding = mPulseExpansionHandler.isExpanding();
2419             if (mTransitioningToFullShadeProgress > 0.0f || pulseExpanding
2420                     || (mQsClippingAnimation != null
2421                         && (mIsQsTranslationResetAnimator || mIsPulseExpansionResetAnimator))) {
2422                 if (pulseExpanding || mIsPulseExpansionResetAnimator) {
2423                     // qsTranslation should only be positive during pulse expansion because it's
2424                     // already translating in from the top
2425                     qsTranslation = Math.max(0, (top - mQs.getHeader().getHeight()) / 2.0f);
2426                 } else if (!mShouldUseSplitNotificationShade) {
2427                     qsTranslation = (top - mQs.getHeader().getHeight()) * QS_PARALLAX_AMOUNT;
2428                 }
2429             }
2430             mQsTranslationForFullShadeTransition = qsTranslation;
2431             updateQsFrameTranslation();
2432             float currentTranslation = mQsFrame.getTranslationY();
2433             mQsClipTop = (int) (top - currentTranslation);
2434             mQsClipBottom = (int) (bottom - currentTranslation);
2435             mQsVisible = qsVisible;
2436             mQs.setFancyClipping(
2437                     mQsClipTop,
2438                     mQsClipBottom,
2439                     radius, qsVisible
2440                     && !mShouldUseSplitNotificationShade);
2441         }
2442         mKeyguardStatusViewController.setClipBounds(
2443                 clipStatusView ? mKeyguardStatusAreaClipBounds : null);
2444         if (!qsVisible && mShouldUseSplitNotificationShade) {
2445             // On the lockscreen when qs isn't visible, we don't want the bounds of the shade to
2446             // be visible, otherwise you can see the bounds once swiping up to see bouncer
2447             mScrimController.setNotificationsBounds(0, 0, 0, 0);
2448         } else {
2449             mScrimController.setNotificationsBounds(left, top, right, bottom);
2450         }
2451 
2452         if (mShouldUseSplitNotificationShade) {
2453             mKeyguardStatusBarViewController.setNoTopClipping();
2454         } else {
2455             mKeyguardStatusBarViewController.updateTopClipping(top);
2456         }
2457 
2458         mScrimController.setScrimCornerRadius(radius);
2459         int nsslLeft = left - mNotificationStackScrollLayoutController.getLeft();
2460         int nsslRight = right - mNotificationStackScrollLayoutController.getLeft();
2461         int nsslTop = top - mNotificationStackScrollLayoutController.getTop();
2462         int nsslBottom = bottom;
2463         int bottomRadius = mShouldUseSplitNotificationShade ? radius : 0;
2464         mNotificationStackScrollLayoutController.setRoundedClippingBounds(
2465                 nsslLeft, nsslTop, nsslRight, nsslBottom, radius, bottomRadius);
2466     }
2467 
getQSEdgePosition()2468     private float getQSEdgePosition() {
2469         // TODO: replace StackY with unified calculation
2470         return Math.max(mQuickQsOffsetHeight * mAmbientState.getExpansionFraction(),
2471                 mAmbientState.getStackY() - mAmbientState.getScrollY());
2472     }
2473 
calculateQsBottomPosition(float qsExpansionFraction)2474     private int calculateQsBottomPosition(float qsExpansionFraction) {
2475         if (mTransitioningToFullShadeProgress > 0.0f) {
2476             return mTransitionToFullShadeQSPosition;
2477         } else {
2478             int qsBottomY = (int) getHeaderTranslation() + mQs.getQsMinExpansionHeight();
2479             if (qsExpansionFraction != 0.0) {
2480                 qsBottomY = (int) MathUtils.lerp(
2481                         qsBottomY, mQs.getDesiredHeight(), qsExpansionFraction);
2482             }
2483             return qsBottomY;
2484         }
2485     }
2486 
determineAccessibilityPaneTitle()2487     private String determineAccessibilityPaneTitle() {
2488         if (mQs != null && mQs.isCustomizing()) {
2489             return mResources.getString(R.string.accessibility_desc_quick_settings_edit);
2490         } else if (mQsExpansionHeight != 0.0f && mQsFullyExpanded) {
2491             // Upon initialisation when we are not layouted yet we don't want to announce that we
2492             // are fully expanded, hence the != 0.0f check.
2493             return mResources.getString(R.string.accessibility_desc_quick_settings);
2494         } else if (mBarState == KEYGUARD) {
2495             return mResources.getString(R.string.accessibility_desc_lock_screen);
2496         } else {
2497             return mResources.getString(R.string.accessibility_desc_notification_shade);
2498         }
2499     }
2500 
calculateNotificationsTopPadding()2501     private float calculateNotificationsTopPadding() {
2502         if (mShouldUseSplitNotificationShade && !mKeyguardShowing) {
2503             return 0;
2504         }
2505         if (mKeyguardShowing && (mQsExpandImmediate
2506                 || mIsExpanding && mQsExpandedWhenExpandingStarted)) {
2507 
2508             // Either QS pushes the notifications down when fully expanded, or QS is fully above the
2509             // notifications (mostly on tablets). maxNotificationPadding denotes the normal top
2510             // padding on Keyguard, maxQsPadding denotes the top padding from the quick settings
2511             // panel. We need to take the maximum and linearly interpolate with the panel expansion
2512             // for a nice motion.
2513             int maxNotificationPadding = getKeyguardNotificationStaticPadding();
2514             int maxQsPadding = mQsMaxExpansionHeight;
2515             int max = mBarState == KEYGUARD ? Math.max(
2516                     maxNotificationPadding, maxQsPadding) : maxQsPadding;
2517             return (int) MathUtils.lerp((float) mQsMinExpansionHeight, (float) max,
2518                     getExpandedFraction());
2519         } else if (mQsSizeChangeAnimator != null) {
2520             return Math.max(
2521                     (int) mQsSizeChangeAnimator.getAnimatedValue(),
2522                     getKeyguardNotificationStaticPadding());
2523         } else if (mKeyguardShowing) {
2524             // We can only do the smoother transition on Keyguard when we also are not collapsing
2525             // from a scrolled quick settings.
2526             return MathUtils.lerp((float) getKeyguardNotificationStaticPadding(),
2527                     (float) (mQsMaxExpansionHeight),
2528                     computeQsExpansionFraction());
2529         } else {
2530             return mQsExpansionHeight;
2531         }
2532     }
2533 
2534     /**
2535      * @return the topPadding of notifications when on keyguard not respecting quick settings
2536      * expansion
2537      */
getKeyguardNotificationStaticPadding()2538     private int getKeyguardNotificationStaticPadding() {
2539         if (!mKeyguardShowing) {
2540             return 0;
2541         }
2542         if (!mKeyguardBypassController.getBypassEnabled()) {
2543             return mClockPositionResult.stackScrollerPadding;
2544         }
2545         int collapsedPosition = mHeadsUpInset;
2546         if (!mNotificationStackScrollLayoutController.isPulseExpanding()) {
2547             return collapsedPosition;
2548         } else {
2549             int expandedPosition = mClockPositionResult.stackScrollerPadding;
2550             return (int) MathUtils.lerp(collapsedPosition, expandedPosition,
2551                     mNotificationStackScrollLayoutController.calculateAppearFractionBypass());
2552         }
2553     }
2554 
2555 
requestScrollerTopPaddingUpdate(boolean animate)2556     protected void requestScrollerTopPaddingUpdate(boolean animate) {
2557         mNotificationStackScrollLayoutController.updateTopPadding(
2558                 calculateNotificationsTopPadding(), animate);
2559         if (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled()) {
2560             // update the position of the header
2561             updateQsExpansion();
2562         }
2563     }
2564 
2565     /**
2566      * Set the amount of pixels we have currently dragged down if we're transitioning to the full
2567      * shade. 0.0f means we're not transitioning yet.
2568      */
setTransitionToFullShadeAmount(float pxAmount, boolean animate, long delay)2569     public void setTransitionToFullShadeAmount(float pxAmount, boolean animate, long delay) {
2570         if (animate && !mShouldUseSplitNotificationShade) {
2571             animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
2572                     delay);
2573             mIsQsTranslationResetAnimator = mQsTranslationForFullShadeTransition > 0.0f;
2574         }
2575 
2576         float endPosition = 0;
2577         if (pxAmount > 0.0f) {
2578             if (mNotificationStackScrollLayoutController.getVisibleNotificationCount() == 0
2579                     && !mMediaDataManager.hasActiveMedia()) {
2580                 // No notifications are visible, let's animate to the height of qs instead
2581                 if (mQs != null) {
2582                     // Let's interpolate to the header height instead of the top padding,
2583                     // because the toppadding is way too low because of the large clock.
2584                     // we still want to take into account the edgePosition though as that nicely
2585                     // overshoots in the stackscroller
2586                     endPosition = getQSEdgePosition()
2587                             - mNotificationStackScrollLayoutController.getTopPadding()
2588                             + mQs.getHeader().getHeight();
2589                 }
2590             } else {
2591                 // Interpolating to the new bottom edge position!
2592                 endPosition = getQSEdgePosition()
2593                         + mNotificationStackScrollLayoutController.getFullShadeTransitionInset();
2594                 if (isOnKeyguard()) {
2595                     endPosition -= mLockscreenNotificationQSPadding;
2596                 }
2597             }
2598         }
2599 
2600         // Calculate the overshoot amount such that we're reaching the target after our desired
2601         // distance, but only reach it fully once we drag a full shade length.
2602         mTransitioningToFullShadeProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
2603                 MathUtils.saturate(pxAmount / mDistanceForQSFullShadeTransition));
2604 
2605         int position = (int) MathUtils.lerp((float) 0, endPosition,
2606                 mTransitioningToFullShadeProgress);
2607         if (mTransitioningToFullShadeProgress > 0.0f) {
2608             // we want at least 1 pixel otherwise the panel won't be clipped
2609             position = Math.max(1, position);
2610         }
2611         mTransitionToFullShadeQSPosition = position;
2612         updateQsExpansion();
2613     }
2614 
2615     /**
2616      * Notify the panel that the pulse expansion has finished and that we're going to the full
2617      * shade
2618      */
onPulseExpansionFinished()2619     public void onPulseExpansionFinished() {
2620         animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, 0);
2621         mIsPulseExpansionResetAnimator = true;
2622     }
2623 
2624     /**
2625      * Set the alpha of the keyguard elements which only show on the lockscreen, but not in
2626      * shade locked / shade. This is used when dragging down to the full shade.
2627      */
setKeyguardOnlyContentAlpha(float keyguardAlpha)2628     public void setKeyguardOnlyContentAlpha(float keyguardAlpha) {
2629         mKeyguardOnlyContentAlpha = Interpolators.ALPHA_IN.getInterpolation(keyguardAlpha);
2630         if (mBarState == KEYGUARD) {
2631             // If the animator is running, it's already fading out the content and this is a reset
2632             mBottomAreaShadeAlpha = mKeyguardOnlyContentAlpha;
2633             updateKeyguardBottomAreaAlpha();
2634         }
2635         updateClock();
2636     }
2637 
trackMovement(MotionEvent event)2638     private void trackMovement(MotionEvent event) {
2639         if (mQsVelocityTracker != null) mQsVelocityTracker.addMovement(event);
2640     }
2641 
initVelocityTracker()2642     private void initVelocityTracker() {
2643         if (mQsVelocityTracker != null) {
2644             mQsVelocityTracker.recycle();
2645         }
2646         mQsVelocityTracker = VelocityTracker.obtain();
2647     }
2648 
getCurrentQSVelocity()2649     private float getCurrentQSVelocity() {
2650         if (mQsVelocityTracker == null) {
2651             return 0;
2652         }
2653         mQsVelocityTracker.computeCurrentVelocity(1000);
2654         return mQsVelocityTracker.getYVelocity();
2655     }
2656 
cancelQsAnimation()2657     private void cancelQsAnimation() {
2658         if (mQsExpansionAnimator != null) {
2659             mQsExpansionAnimator.cancel();
2660         }
2661     }
2662 
2663     /**
2664      * @see #flingSettings(float, int, Runnable, boolean)
2665      */
flingSettings(float vel, int type)2666     public void flingSettings(float vel, int type) {
2667         flingSettings(vel, type, null /* onFinishRunnable */, false /* isClick */);
2668     }
2669 
2670     /**
2671      * Animates QS or QQS as if the user had swiped up or down.
2672      *
2673      * @param vel              Finger velocity or 0 when not initiated by touch events.
2674      * @param type             Either {@link #FLING_EXPAND}, {@link #FLING_COLLAPSE} or {@link
2675      *                         #FLING_HIDE}.
2676      * @param onFinishRunnable Runnable to be executed at the end of animation.
2677      * @param isClick          If originated by click (different interpolator and duration.)
2678      */
flingSettings(float vel, int type, final Runnable onFinishRunnable, boolean isClick)2679     protected void flingSettings(float vel, int type, final Runnable onFinishRunnable,
2680             boolean isClick) {
2681         float target;
2682         switch (type) {
2683             case FLING_EXPAND:
2684                 target = mQsMaxExpansionHeight;
2685                 break;
2686             case FLING_COLLAPSE:
2687                 target = mQsMinExpansionHeight;
2688                 break;
2689             case FLING_HIDE:
2690             default:
2691                 if (mQs != null) {
2692                     mQs.closeDetail();
2693                 }
2694                 target = 0;
2695         }
2696         if (target == mQsExpansionHeight) {
2697             if (onFinishRunnable != null) {
2698                 onFinishRunnable.run();
2699             }
2700             traceQsJank(false /* startTracing */, type != FLING_EXPAND /* wasCancelled */);
2701             return;
2702         }
2703 
2704         // If we move in the opposite direction, reset velocity and use a different duration.
2705         boolean oppositeDirection = false;
2706         boolean expanding = type == FLING_EXPAND;
2707         if (vel > 0 && !expanding || vel < 0 && expanding) {
2708             vel = 0;
2709             oppositeDirection = true;
2710         }
2711         ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
2712         if (isClick) {
2713             animator.setInterpolator(Interpolators.TOUCH_RESPONSE);
2714             animator.setDuration(368);
2715         } else {
2716             mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
2717         }
2718         if (oppositeDirection) {
2719             animator.setDuration(350);
2720         }
2721         animator.addUpdateListener(animation -> {
2722             setQsExpansion((Float) animation.getAnimatedValue());
2723         });
2724         animator.addListener(new AnimatorListenerAdapter() {
2725             private boolean mIsCanceled;
2726             @Override
2727             public void onAnimationStart(Animator animation) {
2728                 notifyExpandingStarted();
2729             }
2730 
2731             @Override
2732             public void onAnimationCancel(Animator animation) {
2733                 mIsCanceled = true;
2734             }
2735 
2736             @Override
2737             public void onAnimationEnd(Animator animation) {
2738                 mQSAnimatingHiddenFromCollapsed = false;
2739                 mAnimatingQS = false;
2740                 notifyExpandingFinished();
2741                 mNotificationStackScrollLayoutController.resetCheckSnoozeLeavebehind();
2742                 mQsExpansionAnimator = null;
2743                 if (onFinishRunnable != null) {
2744                     onFinishRunnable.run();
2745                 }
2746                 traceQsJank(false /* startTracing */, mIsCanceled /* wasCancelled */);
2747             }
2748         });
2749         // Let's note that we're animating QS. Moving the animator here will cancel it immediately,
2750         // so we need a separate flag.
2751         mAnimatingQS = true;
2752         animator.start();
2753         mQsExpansionAnimator = animator;
2754         mQsAnimatorExpand = expanding;
2755         mQSAnimatingHiddenFromCollapsed = computeQsExpansionFraction() == 0.0f && target == 0;
2756     }
2757 
2758     /**
2759      * @return Whether we should intercept a gesture to open Quick Settings.
2760      */
shouldQuickSettingsIntercept(float x, float y, float yDiff)2761     private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
2762         if (!isQsExpansionEnabled() || mCollapsedOnDown
2763                 || (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled())
2764                 || (mKeyguardShowing && mShouldUseSplitNotificationShade)) {
2765             return false;
2766         }
2767         View header = mKeyguardShowing || mQs == null ? mKeyguardStatusBar : mQs.getHeader();
2768 
2769         mQsInterceptRegion.set(
2770                 /* left= */ (int) mQsFrame.getX(),
2771                 /* top= */ header.getTop(),
2772                 /* right= */ (int) mQsFrame.getX() + mQsFrame.getWidth(),
2773                 /* bottom= */ header.getBottom());
2774         // Also allow QS to intercept if the touch is near the notch.
2775         mStatusBarTouchableRegionManager.updateRegionForNotch(mQsInterceptRegion);
2776         final boolean onHeader = mQsInterceptRegion.contains((int) x, (int) y);
2777 
2778         if (mQsExpanded) {
2779             return onHeader || (yDiff < 0 && isInQsArea(x, y));
2780         } else {
2781             return onHeader;
2782         }
2783     }
2784 
2785     @Override
canCollapsePanelOnTouch()2786     protected boolean canCollapsePanelOnTouch() {
2787         if (!isInSettings() && mBarState == KEYGUARD) {
2788             return true;
2789         }
2790 
2791         if (mNotificationStackScrollLayoutController.isScrolledToBottom()) {
2792             return true;
2793         }
2794 
2795         return !mShouldUseSplitNotificationShade && (isInSettings() || mIsPanelCollapseOnQQS);
2796     }
2797 
2798     @Override
getMaxPanelHeight()2799     protected int getMaxPanelHeight() {
2800         int min = mStatusBarMinHeight;
2801         if (!(mBarState == KEYGUARD)
2802                 && mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0) {
2803             int minHeight = mQsMinExpansionHeight;
2804             min = Math.max(min, minHeight);
2805         }
2806         int maxHeight;
2807         if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted
2808                 || mPulsing) {
2809             maxHeight = calculatePanelHeightQsExpanded();
2810         } else {
2811             maxHeight = calculatePanelHeightShade();
2812         }
2813         maxHeight = Math.max(min, maxHeight);
2814         if (maxHeight == 0 || isNaN(maxHeight)) {
2815             Log.wtf(TAG, "maxPanelHeight is invalid. mOverExpansion: "
2816                     + mOverExpansion + ", calculatePanelHeightQsExpanded: "
2817                     + calculatePanelHeightQsExpanded() + ", calculatePanelHeightShade: "
2818                     + calculatePanelHeightShade() + ", mStatusBarMinHeight = "
2819                     + mStatusBarMinHeight + ", mQsMinExpansionHeight = " + mQsMinExpansionHeight);
2820         }
2821         return maxHeight;
2822     }
2823 
isInSettings()2824     public boolean isInSettings() {
2825         return mQsExpanded;
2826     }
2827 
isExpanding()2828     public boolean isExpanding() {
2829         return mIsExpanding;
2830     }
2831 
2832     @Override
onHeightUpdated(float expandedHeight)2833     protected void onHeightUpdated(float expandedHeight) {
2834         if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) {
2835             // Updating the clock position will set the top padding which might
2836             // trigger a new panel height and re-position the clock.
2837             // This is a circular dependency and should be avoided, otherwise we'll have
2838             // a stack overflow.
2839             if (mStackScrollerMeasuringPass > 2) {
2840                 if (DEBUG) Log.d(TAG, "Unstable notification panel height. Aborting.");
2841             } else {
2842                 positionClockAndNotifications();
2843             }
2844         }
2845         if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
2846                 && !mQsExpansionFromOverscroll) {
2847             float t;
2848             if (mKeyguardShowing) {
2849 
2850                 // On Keyguard, interpolate the QS expansion linearly to the panel expansion
2851                 t = expandedHeight / (getMaxPanelHeight());
2852             } else {
2853                 // In Shade, interpolate linearly such that QS is closed whenever panel height is
2854                 // minimum QS expansion + minStackHeight
2855                 float
2856                         panelHeightQsCollapsed =
2857                         mNotificationStackScrollLayoutController.getIntrinsicPadding()
2858                                 + mNotificationStackScrollLayoutController.getLayoutMinHeight();
2859                 float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
2860                 t =
2861                         (expandedHeight - panelHeightQsCollapsed) / (panelHeightQsExpanded
2862                                 - panelHeightQsCollapsed);
2863             }
2864             float
2865                     targetHeight =
2866                     mQsMinExpansionHeight + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight);
2867             setQsExpansion(targetHeight);
2868         }
2869         updateExpandedHeight(expandedHeight);
2870         updateHeader();
2871         updateNotificationTranslucency();
2872         updatePanelExpanded();
2873         updateGestureExclusionRect();
2874         if (DEBUG) {
2875             mView.invalidate();
2876         }
2877     }
2878 
updatePanelExpanded()2879     private void updatePanelExpanded() {
2880         boolean isExpanded = !isFullyCollapsed() || mExpectingSynthesizedDown;
2881         if (mPanelExpanded != isExpanded) {
2882             mHeadsUpManager.setIsPanelExpanded(isExpanded);
2883             mStatusBarTouchableRegionManager.setPanelExpanded(isExpanded);
2884             mStatusBar.setPanelExpanded(isExpanded);
2885             mPanelExpanded = isExpanded;
2886 
2887             if (!isExpanded && mQs != null && mQs.isCustomizing()) {
2888                 mQs.closeCustomizer();
2889             }
2890         }
2891     }
2892 
calculatePanelHeightShade()2893     private int calculatePanelHeightShade() {
2894         int emptyBottomMargin = mNotificationStackScrollLayoutController.getEmptyBottomMargin();
2895         int maxHeight = mNotificationStackScrollLayoutController.getHeight() - emptyBottomMargin;
2896 
2897         if (mBarState == KEYGUARD) {
2898             int minKeyguardPanelBottom = mClockPositionAlgorithm.getLockscreenStatusViewHeight()
2899                     + mNotificationStackScrollLayoutController.getIntrinsicContentHeight();
2900             return Math.max(maxHeight, minKeyguardPanelBottom);
2901         } else {
2902             return maxHeight;
2903         }
2904     }
2905 
calculatePanelHeightQsExpanded()2906     private int calculatePanelHeightQsExpanded() {
2907         float
2908                 notificationHeight =
2909                 mNotificationStackScrollLayoutController.getHeight()
2910                         - mNotificationStackScrollLayoutController.getEmptyBottomMargin()
2911                         - mNotificationStackScrollLayoutController.getTopPadding();
2912 
2913         // When only empty shade view is visible in QS collapsed state, simulate that we would have
2914         // it in expanded QS state as well so we don't run into troubles when fading the view in/out
2915         // and expanding/collapsing the whole panel from/to quick settings.
2916         if (mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0
2917                 && mNotificationStackScrollLayoutController.isShowingEmptyShadeView()) {
2918             notificationHeight = mNotificationStackScrollLayoutController.getEmptyShadeViewHeight();
2919         }
2920         int maxQsHeight = mQsMaxExpansionHeight;
2921 
2922         // If an animation is changing the size of the QS panel, take the animated value.
2923         if (mQsSizeChangeAnimator != null) {
2924             maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
2925         }
2926         float totalHeight = Math.max(maxQsHeight,
2927                 mBarState == KEYGUARD ? mClockPositionResult.stackScrollerPadding
2928                         : 0) + notificationHeight
2929                 + mNotificationStackScrollLayoutController.getTopPaddingOverflow();
2930         if (totalHeight > mNotificationStackScrollLayoutController.getHeight()) {
2931             float
2932                     fullyCollapsedHeight =
2933                     maxQsHeight + mNotificationStackScrollLayoutController.getLayoutMinHeight();
2934             totalHeight = Math.max(fullyCollapsedHeight,
2935                     mNotificationStackScrollLayoutController.getHeight());
2936         }
2937         return (int) totalHeight;
2938     }
2939 
updateNotificationTranslucency()2940     private void updateNotificationTranslucency() {
2941         float alpha = 1f;
2942         if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp
2943                 && !mHeadsUpManager.hasPinnedHeadsUp()) {
2944             alpha = getFadeoutAlpha();
2945         }
2946         if (mBarState == KEYGUARD && !mHintAnimationRunning
2947                 && !mKeyguardBypassController.getBypassEnabled()) {
2948             alpha *= mClockPositionResult.clockAlpha;
2949         }
2950         mNotificationStackScrollLayoutController.setAlpha(alpha);
2951     }
2952 
getFadeoutAlpha()2953     private float getFadeoutAlpha() {
2954         float alpha;
2955         if (mQsMinExpansionHeight == 0) {
2956             return 1.0f;
2957         }
2958         alpha = getExpandedHeight() / mQsMinExpansionHeight;
2959         alpha = Math.max(0, Math.min(alpha, 1));
2960         alpha = (float) Math.pow(alpha, 0.75);
2961         return alpha;
2962     }
2963 
2964     /**
2965      * Hides the header when notifications are colliding with it.
2966      */
updateHeader()2967     private void updateHeader() {
2968         if (mBarState == KEYGUARD) {
2969             mKeyguardStatusBarViewController.updateViewState();
2970         }
2971         updateQsExpansion();
2972     }
2973 
getHeaderTranslation()2974     protected float getHeaderTranslation() {
2975         if (mBarState == KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) {
2976             return -mQs.getQsMinExpansionHeight();
2977         }
2978         float appearAmount = mNotificationStackScrollLayoutController
2979                 .calculateAppearFraction(mExpandedHeight);
2980         float startHeight = -mQsExpansionHeight;
2981         if (!mShouldUseSplitNotificationShade && mBarState == StatusBarState.SHADE) {
2982             // Small parallax as we pull down and clip QS
2983             startHeight = -mQsExpansionHeight * QS_PARALLAX_AMOUNT;
2984         }
2985         if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()) {
2986             appearAmount = mNotificationStackScrollLayoutController.calculateAppearFractionBypass();
2987             startHeight = -mQs.getQsMinExpansionHeight();
2988         }
2989         float translation = MathUtils.lerp(startHeight, 0, Math.min(1.0f, appearAmount));
2990         return Math.min(0, translation);
2991     }
2992 
updateKeyguardBottomAreaAlpha()2993     private void updateKeyguardBottomAreaAlpha() {
2994         // There are two possible panel expansion behaviors:
2995         // • User dragging up to unlock: we want to fade out as quick as possible
2996         //   (ALPHA_EXPANSION_THRESHOLD) to avoid seeing the bouncer over the bottom area.
2997         // • User tapping on lock screen: bouncer won't be visible but panel expansion will
2998         //   change due to "unlock hint animation." In this case, fading out the bottom area
2999         //   would also hide the message that says "swipe to unlock," we don't want to do that.
3000         float expansionAlpha = MathUtils.map(
3001                 isUnlockHintRunning() ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f,
3002                 getExpandedFraction());
3003         float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
3004         alpha *= mBottomAreaShadeAlpha;
3005         mKeyguardBottomArea.setAffordanceAlpha(alpha);
3006         mKeyguardBottomArea.setImportantForAccessibility(
3007                 alpha == 0f ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
3008                         : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
3009         View ambientIndicationContainer = mStatusBar.getAmbientIndicationContainer();
3010         if (ambientIndicationContainer != null) {
3011             ambientIndicationContainer.setAlpha(alpha);
3012         }
3013         mLockIconViewController.setAlpha(alpha);
3014     }
3015 
3016     @Override
onExpandingStarted()3017     protected void onExpandingStarted() {
3018         super.onExpandingStarted();
3019         mNotificationStackScrollLayoutController.onExpansionStarted();
3020         mIsExpanding = true;
3021         mQsExpandedWhenExpandingStarted = mQsFullyExpanded;
3022         mMediaHierarchyManager.setCollapsingShadeFromQS(mQsExpandedWhenExpandingStarted &&
3023                 /* We also start expanding when flinging closed Qs. Let's exclude that */
3024                 !mAnimatingQS);
3025         if (mQsExpanded) {
3026             onQsExpansionStarted();
3027         }
3028         // Since there are QS tiles in the header now, we need to make sure we start listening
3029         // immediately so they can be up to date.
3030         if (mQs == null) return;
3031         mQs.setHeaderListening(true);
3032     }
3033 
3034     @Override
onExpandingFinished()3035     protected void onExpandingFinished() {
3036         mScrimController.onExpandingFinished();
3037         mNotificationStackScrollLayoutController.onExpansionStopped();
3038         mHeadsUpManager.onExpandingFinished();
3039         mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
3040         mIsExpanding = false;
3041         mMediaHierarchyManager.setCollapsingShadeFromQS(false);
3042         mMediaHierarchyManager.setQsExpanded(mQsExpanded);
3043         if (isFullyCollapsed()) {
3044             DejankUtils.postAfterTraversal(new Runnable() {
3045                 @Override
3046                 public void run() {
3047                     setListening(false);
3048                 }
3049             });
3050 
3051             // Workaround b/22639032: Make sure we invalidate something because else RenderThread
3052             // thinks we are actually drawing a frame put in reality we don't, so RT doesn't go
3053             // ahead with rendering and we jank.
3054             mView.postOnAnimation(new Runnable() {
3055                 @Override
3056                 public void run() {
3057                     mView.getParent().invalidateChild(mView, M_DUMMY_DIRTY_RECT);
3058                 }
3059             });
3060         } else {
3061             setListening(true);
3062         }
3063         mQsExpandImmediate = false;
3064         mNotificationStackScrollLayoutController.setShouldShowShelfOnly(false);
3065         mTwoFingerQsExpandPossible = false;
3066         updateTrackingHeadsUp(null);
3067         mExpandingFromHeadsUp = false;
3068         setPanelScrimMinFraction(0.0f);
3069     }
3070 
updateTrackingHeadsUp(@ullable ExpandableNotificationRow pickedChild)3071     private void updateTrackingHeadsUp(@Nullable ExpandableNotificationRow pickedChild) {
3072         mTrackedHeadsUpNotification = pickedChild;
3073         for (int i = 0; i < mTrackingHeadsUpListeners.size(); i++) {
3074             Consumer<ExpandableNotificationRow> listener = mTrackingHeadsUpListeners.get(i);
3075             listener.accept(pickedChild);
3076         }
3077     }
3078 
3079     @Nullable
getTrackedHeadsUpNotification()3080     public ExpandableNotificationRow getTrackedHeadsUpNotification() {
3081         return mTrackedHeadsUpNotification;
3082     }
3083 
setListening(boolean listening)3084     private void setListening(boolean listening) {
3085         mKeyguardStatusBarViewController.setBatteryListening(listening);
3086         if (mQs == null) return;
3087         mQs.setListening(listening);
3088     }
3089 
3090     @Override
expand(boolean animate)3091     public void expand(boolean animate) {
3092         super.expand(animate);
3093         setListening(true);
3094     }
3095 
3096     @Override
setOverExpansion(float overExpansion)3097     public void setOverExpansion(float overExpansion) {
3098         if (overExpansion == mOverExpansion) {
3099             return;
3100         }
3101         super.setOverExpansion(overExpansion);
3102         // Translating the quick settings by half the overexpansion to center it in the background
3103         // frame
3104         updateQsFrameTranslation();
3105         mNotificationStackScrollLayoutController.setOverExpansion(overExpansion);
3106     }
3107 
updateQsFrameTranslation()3108     private void updateQsFrameTranslation() {
3109         float translation = mOverExpansion / 2.0f + mQsTranslationForFullShadeTransition;
3110         mQsFrame.setTranslationY(translation);
3111     }
3112 
3113     @Override
onTrackingStarted()3114     protected void onTrackingStarted() {
3115         mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
3116         super.onTrackingStarted();
3117         mScrimController.onTrackingStarted();
3118         if (mQsFullyExpanded) {
3119             mQsExpandImmediate = true;
3120             if (!mShouldUseSplitNotificationShade) {
3121                 mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
3122             }
3123         }
3124         if (mBarState == KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
3125             mAffordanceHelper.animateHideLeftRightIcon();
3126         }
3127         mNotificationStackScrollLayoutController.onPanelTrackingStarted();
3128         cancelPendingPanelCollapse();
3129     }
3130 
3131     @Override
onTrackingStopped(boolean expand)3132     protected void onTrackingStopped(boolean expand) {
3133         mFalsingCollector.onTrackingStopped();
3134         super.onTrackingStopped(expand);
3135         if (expand) {
3136             mNotificationStackScrollLayoutController.setOverScrollAmount(0.0f, true /* onTop */,
3137                     true /* animate */);
3138         }
3139         mNotificationStackScrollLayoutController.onPanelTrackingStopped();
3140         if (expand && (mBarState == KEYGUARD
3141                 || mBarState == StatusBarState.SHADE_LOCKED)) {
3142             if (!mHintAnimationRunning) {
3143                 mAffordanceHelper.reset(true);
3144             }
3145         }
3146     }
3147 
updateMaxHeadsUpTranslation()3148     private void updateMaxHeadsUpTranslation() {
3149         mNotificationStackScrollLayoutController.setHeadsUpBoundaries(
3150                 getHeight(), mNavigationBarBottomHeight);
3151     }
3152 
3153     @Override
startUnlockHintAnimation()3154     protected void startUnlockHintAnimation() {
3155         if (mPowerManager.isPowerSaveMode()) {
3156             onUnlockHintStarted();
3157             onUnlockHintFinished();
3158             return;
3159         }
3160         super.startUnlockHintAnimation();
3161     }
3162 
3163     @Override
onUnlockHintFinished()3164     protected void onUnlockHintFinished() {
3165         super.onUnlockHintFinished();
3166         mNotificationStackScrollLayoutController.setUnlockHintRunning(false);
3167     }
3168 
3169     @Override
onUnlockHintStarted()3170     protected void onUnlockHintStarted() {
3171         super.onUnlockHintStarted();
3172         mNotificationStackScrollLayoutController.setUnlockHintRunning(true);
3173     }
3174 
3175     @Override
shouldUseDismissingAnimation()3176     protected boolean shouldUseDismissingAnimation() {
3177         return mBarState != StatusBarState.SHADE && (mKeyguardStateController.canDismissLockScreen()
3178                 || !isTracking());
3179     }
3180 
3181     @Override
isTrackingBlocked()3182     protected boolean isTrackingBlocked() {
3183         return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch;
3184     }
3185 
isQsExpanded()3186     public boolean isQsExpanded() {
3187         return mQsExpanded;
3188     }
3189 
isQsDetailShowing()3190     public boolean isQsDetailShowing() {
3191         return mQs.isShowingDetail();
3192     }
3193 
3194     /** Returns whether the QS customizer is currently active. */
isQsCustomizing()3195     public boolean isQsCustomizing() {
3196         return mQs.isCustomizing();
3197     }
3198 
closeQsDetail()3199     public void closeQsDetail() {
3200         mQs.closeDetail();
3201     }
3202 
3203     /** Close the QS customizer if it is open. */
closeQsCustomizer()3204     public void closeQsCustomizer() {
3205         mQs.closeCustomizer();
3206     }
3207 
isLaunchTransitionFinished()3208     public boolean isLaunchTransitionFinished() {
3209         return mIsLaunchTransitionFinished;
3210     }
3211 
isLaunchTransitionRunning()3212     public boolean isLaunchTransitionRunning() {
3213         return mIsLaunchTransitionRunning;
3214     }
3215 
setLaunchTransitionEndRunnable(Runnable r)3216     public void setLaunchTransitionEndRunnable(Runnable r) {
3217         mLaunchAnimationEndRunnable = r;
3218     }
3219 
updateDozingVisibilities(boolean animate)3220     private void updateDozingVisibilities(boolean animate) {
3221         mKeyguardBottomArea.setDozing(mDozing, animate);
3222         if (!mDozing && animate) {
3223             mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
3224         }
3225     }
3226 
3227     @Override
isDozing()3228     public boolean isDozing() {
3229         return mDozing;
3230     }
3231 
setQsScrimEnabled(boolean qsScrimEnabled)3232     public void setQsScrimEnabled(boolean qsScrimEnabled) {
3233         boolean changed = mQsScrimEnabled != qsScrimEnabled;
3234         mQsScrimEnabled = qsScrimEnabled;
3235         if (changed) {
3236             updateQsState();
3237         }
3238     }
3239 
onScreenTurningOn()3240     public void onScreenTurningOn() {
3241         mKeyguardStatusViewController.dozeTimeTick();
3242     }
3243 
3244     @Override
onMiddleClicked()3245     protected boolean onMiddleClicked() {
3246         switch (mBarState) {
3247             case KEYGUARD:
3248                 if (!mDozingOnDown) {
3249                     if (mUpdateMonitor.isFaceEnrolled()
3250                             && !mUpdateMonitor.isFaceDetectionRunning()
3251                             && !mUpdateMonitor.getUserCanSkipBouncer(
3252                                     KeyguardUpdateMonitor.getCurrentUser())) {
3253                         mUpdateMonitor.requestFaceAuth(true);
3254                     } else {
3255                         mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT,
3256                                 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
3257                         mLockscreenGestureLogger
3258                             .log(LockscreenUiEvent.LOCKSCREEN_LOCK_SHOW_HINT);
3259                         startUnlockHintAnimation();
3260                     }
3261                 }
3262                 return true;
3263             case StatusBarState.SHADE_LOCKED:
3264                 if (!mQsExpanded) {
3265                     mStatusBarStateController.setState(KEYGUARD);
3266                 }
3267                 return true;
3268             default:
3269                 return true;
3270         }
3271     }
3272 
setPanelAlpha(int alpha, boolean animate)3273     public void setPanelAlpha(int alpha, boolean animate) {
3274         if (mPanelAlpha != alpha) {
3275             mPanelAlpha = alpha;
3276             PropertyAnimator.setProperty(mView, mPanelAlphaAnimator, alpha, alpha == 255
3277                             ? mPanelAlphaInPropertiesAnimator : mPanelAlphaOutPropertiesAnimator,
3278                     animate);
3279         }
3280     }
3281 
setPanelAlphaEndAction(Runnable r)3282     public void setPanelAlphaEndAction(Runnable r) {
3283         mPanelAlphaEndAction = r;
3284     }
3285 
setHeadsUpAnimatingAway(boolean headsUpAnimatingAway)3286     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
3287         mHeadsUpAnimatingAway = headsUpAnimatingAway;
3288         mNotificationStackScrollLayoutController.setHeadsUpAnimatingAway(headsUpAnimatingAway);
3289         updateVisibility();
3290     }
3291 
3292     /** Set whether the bouncer is showing. */
setBouncerShowing(boolean bouncerShowing)3293     public void setBouncerShowing(boolean bouncerShowing) {
3294         mBouncerShowing = bouncerShowing;
3295         updateVisibility();
3296     }
3297 
3298     @Override
shouldPanelBeVisible()3299     protected boolean shouldPanelBeVisible() {
3300         boolean headsUpVisible = mHeadsUpAnimatingAway || mHeadsUpPinnedMode;
3301         return headsUpVisible || isExpanded() || mBouncerShowing;
3302     }
3303 
3304     @Override
setHeadsUpManager(HeadsUpManagerPhone headsUpManager)3305     public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
3306         super.setHeadsUpManager(headsUpManager);
3307         mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
3308                 mNotificationStackScrollLayoutController.getHeadsUpCallback(),
3309                 NotificationPanelViewController.this);
3310     }
3311 
setTrackedHeadsUp(ExpandableNotificationRow pickedChild)3312     public void setTrackedHeadsUp(ExpandableNotificationRow pickedChild) {
3313         if (pickedChild != null) {
3314             updateTrackingHeadsUp(pickedChild);
3315             mExpandingFromHeadsUp = true;
3316         }
3317         // otherwise we update the state when the expansion is finished
3318     }
3319 
3320     @Override
onClosingFinished()3321     protected void onClosingFinished() {
3322         mStatusBar.onClosingFinished();
3323         setClosingWithAlphaFadeout(false);
3324         mMediaHierarchyManager.closeGuts();
3325     }
3326 
setClosingWithAlphaFadeout(boolean closing)3327     private void setClosingWithAlphaFadeout(boolean closing) {
3328         mClosingWithAlphaFadeOut = closing;
3329         mNotificationStackScrollLayoutController.forceNoOverlappingRendering(closing);
3330     }
3331 
updateExpandedHeight(float expandedHeight)3332     protected void updateExpandedHeight(float expandedHeight) {
3333         if (mTracking) {
3334             mNotificationStackScrollLayoutController
3335                     .setExpandingVelocity(getCurrentExpandVelocity());
3336         }
3337         if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()) {
3338             // The expandedHeight is always the full panel Height when bypassing
3339             expandedHeight = getMaxPanelHeight();
3340         }
3341         mNotificationStackScrollLayoutController.setExpandedHeight(expandedHeight);
3342         updateKeyguardBottomAreaAlpha();
3343         updateStatusBarIcons();
3344     }
3345 
3346     /**
3347      * @return whether the notifications are displayed full width and don't have any margins on
3348      * the side.
3349      */
isFullWidth()3350     public boolean isFullWidth() {
3351         return mIsFullWidth;
3352     }
3353 
updateStatusBarIcons()3354     private void updateStatusBarIcons() {
3355         boolean
3356                 showIconsWhenExpanded =
3357                 (isPanelVisibleBecauseOfHeadsUp() || isFullWidth())
3358                         && getExpandedHeight() < getOpeningHeight();
3359         boolean noVisibleNotifications = true;
3360         if (showIconsWhenExpanded && noVisibleNotifications && isOnKeyguard()) {
3361             showIconsWhenExpanded = false;
3362         }
3363         if (showIconsWhenExpanded != mShowIconsWhenExpanded) {
3364             mShowIconsWhenExpanded = showIconsWhenExpanded;
3365             mCommandQueue.recomputeDisableFlags(mDisplayId, false);
3366         }
3367     }
3368 
3369     private boolean isOnKeyguard() {
3370         return mBarState == KEYGUARD;
3371     }
3372 
3373     /**
3374      * Sets the minimum fraction for the panel expansion offset. This may be non-zero in certain
3375      * cases, such as if there's a heads-up notification.
3376      */
3377     public void setPanelScrimMinFraction(float minFraction) {
3378         mMinFraction = minFraction;
3379         mDepthController.setPanelPullDownMinFraction(mMinFraction);
3380         mScrimController.setPanelScrimMinFraction(mMinFraction);
3381     }
3382 
3383     public void clearNotificationEffects() {
3384         mStatusBar.clearNotificationEffects();
3385     }
3386 
3387     @Override
3388     protected boolean isPanelVisibleBecauseOfHeadsUp() {
3389         return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway)
3390                 && mBarState == StatusBarState.SHADE;
3391     }
3392 
3393     public void launchCamera(boolean animate, int source) {
3394         if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP) {
3395             mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP;
3396         } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) {
3397             mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_WIGGLE;
3398         } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER) {
3399             mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER;
3400         } else {
3401 
3402             // Default.
3403             mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
3404         }
3405 
3406         // If we are launching it when we are occluded already we don't want it to animate,
3407         // nor setting these flags, since the occluded state doesn't change anymore, hence it's
3408         // never reset.
3409         if (!isFullyCollapsed()) {
3410             setLaunchingAffordance(true);
3411         } else {
3412             animate = false;
3413         }
3414         mAffordanceHasPreview = mKeyguardBottomArea.getRightPreview() != null;
3415         mAffordanceHelper.launchAffordance(
3416                 animate, mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
3417     }
3418 
3419     public void onAffordanceLaunchEnded() {
3420         setLaunchingAffordance(false);
3421     }
3422 
3423     /**
3424      * Set whether we are currently launching an affordance. This is currently only set when
3425      * launched via a camera gesture.
3426      */
3427     private void setLaunchingAffordance(boolean launchingAffordance) {
3428         mLaunchingAffordance = launchingAffordance;
3429         mKeyguardAffordanceHelperCallback.getLeftIcon().setLaunchingAffordance(launchingAffordance);
3430         mKeyguardAffordanceHelperCallback.getRightIcon().setLaunchingAffordance(
3431                 launchingAffordance);
3432         mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
3433     }
3434 
3435     /**
3436      * Return true when a bottom affordance is launching an occluded activity with a splash screen.
3437      */
3438     public boolean isLaunchingAffordanceWithPreview() {
3439         return mLaunchingAffordance && mAffordanceHasPreview;
3440     }
3441 
3442     /**
3443      * Whether the camera application can be launched for the camera launch gesture.
3444      */
3445     public boolean canCameraGestureBeLaunched() {
3446         if (!mStatusBar.isCameraAllowedByAdmin()) {
3447             return false;
3448         }
3449 
3450         ResolveInfo resolveInfo = mKeyguardBottomArea.resolveCameraIntent();
3451         String
3452                 packageToLaunch =
3453                 (resolveInfo == null || resolveInfo.activityInfo == null) ? null
3454                         : resolveInfo.activityInfo.packageName;
3455         return packageToLaunch != null && (mBarState != StatusBarState.SHADE || !isForegroundApp(
3456                 packageToLaunch)) && !mAffordanceHelper.isSwipingInProgress();
3457     }
3458 
3459     /**
3460      * Return true if the applications with the package name is running in foreground.
3461      *
3462      * @param pkgName application package name.
3463      */
3464     private boolean isForegroundApp(String pkgName) {
3465         List<ActivityManager.RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
3466         return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
3467     }
3468 
3469     public boolean hideStatusBarIconsWhenExpanded() {
3470         if (mIsLaunchAnimationRunning) {
3471             return mHideIconsDuringLaunchAnimation;
3472         }
3473         if (mHeadsUpAppearanceController != null
3474                 && mHeadsUpAppearanceController.shouldBeVisible()) {
3475             return false;
3476         }
3477         return !isFullWidth() || !mShowIconsWhenExpanded;
3478     }
3479 
3480     public final QS.ScrollListener mScrollListener = scrollY -> {
3481         if (scrollY > 0 && !mQsFullyExpanded) {
3482             if (DEBUG) Log.d(TAG, "Scrolling while not expanded. Forcing expand");
3483             // If we are scrolling QS, we should be fully expanded.
3484             expandWithQs();
3485         }
3486     };
3487 
3488     private final FragmentListener mFragmentListener = new FragmentListener() {
3489         @Override
3490         public void onFragmentViewCreated(String tag, Fragment fragment) {
3491             mQs = (QS) fragment;
3492             mQs.setPanelView(mHeightListener);
3493             mQs.setExpandClickListener(mOnClickListener);
3494             mQs.setHeaderClickable(isQsExpansionEnabled());
3495             mQs.setOverscrolling(mStackScrollerOverscrolling);
3496             mQs.setInSplitShade(mShouldUseSplitNotificationShade);
3497 
3498             // recompute internal state when qspanel height changes
3499             mQs.getView().addOnLayoutChangeListener(
3500                     (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
3501                         final int height = bottom - top;
3502                         final int oldHeight = oldBottom - oldTop;
3503                         if (height != oldHeight) {
3504                             mHeightListener.onQsHeightChanged();
3505                         }
3506                     });
3507             mQs.setCollapsedMediaVisibilityChangedListener((visible) -> {
3508                 if (mQs.getHeader().isShown()) {
3509                     animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_STANDARD,
3510                             0 /* delay */);
3511                     mNotificationStackScrollLayoutController.animateNextTopPaddingChange();
3512                 }
3513             });
3514             mLockscreenShadeTransitionController.setQS(mQs);
3515             mNotificationStackScrollLayoutController.setQsContainer((ViewGroup) mQs.getView());
3516             mQs.setScrollListener(mScrollListener);
3517             updateQsExpansion();
3518         }
3519 
3520         @Override
3521         public void onFragmentViewDestroyed(String tag, Fragment fragment) {
3522             // Manual handling of fragment lifecycle is only required because this bridges
3523             // non-fragment and fragment code. Once we are using a fragment for the notification
3524             // panel, mQs will not need to be null cause it will be tied to the same lifecycle.
3525             if (fragment == mQs) {
3526                 mQs = null;
3527             }
3528         }
3529     };
3530 
animateNextNotificationBounds(long duration, long delay)3531     private void animateNextNotificationBounds(long duration, long delay) {
3532         mAnimateNextNotificationBounds = true;
3533         mNotificationBoundsAnimationDuration = duration;
3534         mNotificationBoundsAnimationDelay = delay;
3535     }
3536 
3537     @Override
setTouchAndAnimationDisabled(boolean disabled)3538     public void setTouchAndAnimationDisabled(boolean disabled) {
3539         super.setTouchAndAnimationDisabled(disabled);
3540         if (disabled && mAffordanceHelper.isSwipingInProgress() && !mIsLaunchTransitionRunning) {
3541             mAffordanceHelper.reset(false /* animate */);
3542         }
3543         mNotificationStackScrollLayoutController.setAnimationsEnabled(!disabled);
3544     }
3545 
3546     /**
3547      * Sets the dozing state.
3548      *
3549      * @param dozing              {@code true} when dozing.
3550      * @param animate             if transition should be animated.
3551      * @param wakeUpTouchLocation touch event location - if woken up by SLPI sensor.
3552      */
setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation)3553     public void setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation) {
3554         if (dozing == mDozing) return;
3555         mView.setDozing(dozing);
3556         mDozing = dozing;
3557         mNotificationStackScrollLayoutController.setDozing(mDozing, animate, wakeUpTouchLocation);
3558         mKeyguardBottomArea.setDozing(mDozing, animate);
3559         mKeyguardStatusBarViewController.setDozing(mDozing);
3560 
3561         if (dozing) {
3562             mBottomAreaShadeAlphaAnimator.cancel();
3563         }
3564 
3565         if (mBarState == KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
3566             updateDozingVisibilities(animate);
3567         }
3568 
3569         final float dozeAmount = dozing ? 1 : 0;
3570         mStatusBarStateController.setAndInstrumentDozeAmount(mView, dozeAmount, animate);
3571     }
3572 
setPulsing(boolean pulsing)3573     public void setPulsing(boolean pulsing) {
3574         mPulsing = pulsing;
3575         final boolean
3576                 animatePulse =
3577                 !mDozeParameters.getDisplayNeedsBlanking() && mDozeParameters.getAlwaysOn();
3578         if (animatePulse) {
3579             mAnimateNextPositionUpdate = true;
3580         }
3581         // Do not animate the clock when waking up from a pulse.
3582         // The height callback will take care of pushing the clock to the right position.
3583         if (!mPulsing && !mDozing) {
3584             mAnimateNextPositionUpdate = false;
3585         }
3586         mNotificationStackScrollLayoutController.setPulsing(pulsing, animatePulse);
3587     }
3588 
setAmbientIndicationTop(int ambientIndicationTop, boolean ambientTextVisible)3589     public void setAmbientIndicationTop(int ambientIndicationTop, boolean ambientTextVisible) {
3590         int ambientIndicationBottomPadding = 0;
3591         if (ambientTextVisible) {
3592             int stackBottom = mNotificationStackScrollLayoutController.getView().getBottom();
3593             ambientIndicationBottomPadding = stackBottom - ambientIndicationTop;
3594         }
3595         if (mAmbientIndicationBottomPadding != ambientIndicationBottomPadding) {
3596             mAmbientIndicationBottomPadding = ambientIndicationBottomPadding;
3597             updateMaxDisplayedNotifications(true);
3598         }
3599     }
3600 
dozeTimeTick()3601     public void dozeTimeTick() {
3602         mLockIconViewController.dozeTimeTick();
3603         mKeyguardBottomArea.dozeTimeTick();
3604         mKeyguardStatusViewController.dozeTimeTick();
3605         if (mInterpolatedDarkAmount > 0) {
3606             positionClockAndNotifications();
3607         }
3608     }
3609 
setStatusAccessibilityImportance(int mode)3610     public void setStatusAccessibilityImportance(int mode) {
3611         mKeyguardStatusViewController.setStatusAccessibilityImportance(mode);
3612     }
3613 
3614     /**
3615      * TODO: this should be removed.
3616      * It's not correct to pass this view forward because other classes will end up adding
3617      * children to it. Theme will be out of sync.
3618      *
3619      * @return bottom area view
3620      */
getKeyguardBottomAreaView()3621     public KeyguardBottomAreaView getKeyguardBottomAreaView() {
3622         return mKeyguardBottomArea;
3623     }
3624 
setUserSetupComplete(boolean userSetupComplete)3625     public void setUserSetupComplete(boolean userSetupComplete) {
3626         mUserSetupComplete = userSetupComplete;
3627         mKeyguardBottomArea.setUserSetupComplete(userSetupComplete);
3628     }
3629 
applyLaunchAnimationProgress(float linearProgress)3630     public void applyLaunchAnimationProgress(float linearProgress) {
3631         boolean hideIcons = LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS,
3632                 linearProgress, ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
3633         if (hideIcons != mHideIconsDuringLaunchAnimation) {
3634             mHideIconsDuringLaunchAnimation = hideIcons;
3635             if (!hideIcons) {
3636                 mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
3637             }
3638         }
3639     }
3640 
addTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener)3641     public void addTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
3642         mTrackingHeadsUpListeners.add(listener);
3643     }
3644 
removeTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener)3645     public void removeTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
3646         mTrackingHeadsUpListeners.remove(listener);
3647     }
3648 
setHeadsUpAppearanceController( HeadsUpAppearanceController headsUpAppearanceController)3649     public void setHeadsUpAppearanceController(
3650             HeadsUpAppearanceController headsUpAppearanceController) {
3651         mHeadsUpAppearanceController = headsUpAppearanceController;
3652     }
3653 
3654     /**
3655      * Starts the animation before we dismiss Keyguard, i.e. an disappearing animation on the
3656      * security view of the bouncer.
3657      */
onBouncerPreHideAnimation()3658     public void onBouncerPreHideAnimation() {
3659         if (mKeyguardQsUserSwitchController != null) {
3660             mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
3661                     mBarState,
3662                     true /* keyguardFadingAway */,
3663                     false /* goingToFullShade */,
3664                     mBarState);
3665         }
3666         if (mKeyguardUserSwitcherController != null) {
3667             mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
3668                     mBarState,
3669                     true /* keyguardFadingAway */,
3670                     false /* goingToFullShade */,
3671                     mBarState);
3672         }
3673     }
3674 
3675     /** */
setImportantForAccessibility(int mode)3676     public void setImportantForAccessibility(int mode) {
3677         mView.setImportantForAccessibility(mode);
3678     }
3679 
3680     /**
3681      * Do not let the user drag the shade up and down for the current touch session.
3682      * This is necessary to avoid shade expansion while/after the bouncer is dismissed.
3683      */
blockExpansionForCurrentTouch()3684     public void blockExpansionForCurrentTouch() {
3685         mBlockingExpansionForCurrentTouch = mTracking;
3686     }
3687 
3688     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)3689     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
3690         super.dump(fd, pw, args);
3691         pw.println("    gestureExclusionRect: " + calculateGestureExclusionRect()
3692                 + " applyQSClippingImmediately: top(" + mQsClipTop + ") bottom(" + mQsClipBottom
3693                 + ") qsVisible(" + mQsVisible);
3694         if (mKeyguardStatusBarViewController != null) {
3695             mKeyguardStatusBarViewController.dump(fd, pw, args);
3696         }
3697     }
3698 
hasActiveClearableNotifications()3699     public boolean hasActiveClearableNotifications() {
3700         return mNotificationStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL);
3701     }
3702 
createRemoteInputDelegate()3703     public RemoteInputController.Delegate createRemoteInputDelegate() {
3704         return mNotificationStackScrollLayoutController.createDelegate();
3705     }
3706 
3707     /**
3708      * Updates the notification views' sections and status bar icons. This is
3709      * triggered by the NotificationPresenter whenever there are changes to the underlying
3710      * notification data being displayed. In the new notification pipeline, this is handled in
3711      * {@link ShadeViewManager}.
3712      */
updateNotificationViews(String reason)3713     public void updateNotificationViews(String reason) {
3714         mNotificationStackScrollLayoutController.updateSectionBoundaries(reason);
3715         mNotificationStackScrollLayoutController.updateFooter();
3716 
3717         mNotificationIconAreaController.updateNotificationIcons(createVisibleEntriesList());
3718     }
3719 
createVisibleEntriesList()3720     private List<ListEntry> createVisibleEntriesList() {
3721         List<ListEntry> entries = new ArrayList<>(
3722                 mNotificationStackScrollLayoutController.getChildCount());
3723         for (int i = 0; i < mNotificationStackScrollLayoutController.getChildCount(); i++) {
3724             View view = mNotificationStackScrollLayoutController.getChildAt(i);
3725             if (view instanceof ExpandableNotificationRow) {
3726                 entries.add(((ExpandableNotificationRow) view).getEntry());
3727             }
3728         }
3729         return entries;
3730     }
3731 
onUpdateRowStates()3732     public void onUpdateRowStates() {
3733         mNotificationStackScrollLayoutController.onUpdateRowStates();
3734     }
3735 
hasPulsingNotifications()3736     public boolean hasPulsingNotifications() {
3737         return mNotificationStackScrollLayoutController
3738                 .getNotificationListContainer().hasPulsingNotifications();
3739     }
3740 
getActivatedChild()3741     public ActivatableNotificationView getActivatedChild() {
3742         return mNotificationStackScrollLayoutController.getActivatedChild();
3743     }
3744 
setActivatedChild(ActivatableNotificationView o)3745     public void setActivatedChild(ActivatableNotificationView o) {
3746         mNotificationStackScrollLayoutController.setActivatedChild(o);
3747     }
3748 
runAfterAnimationFinished(Runnable r)3749     public void runAfterAnimationFinished(Runnable r) {
3750         mNotificationStackScrollLayoutController.runAfterAnimationFinished(r);
3751     }
3752 
setScrollingEnabled(boolean b)3753     public void setScrollingEnabled(boolean b) {
3754         mNotificationStackScrollLayoutController.setScrollingEnabled(b);
3755     }
3756 
3757     private Runnable mHideExpandedRunnable;
3758     private final Runnable mMaybeHideExpandedRunnable = new Runnable() {
3759         @Override
3760         public void run() {
3761             if (getExpansionFraction() == 0.0f) {
3762                 mView.post(mHideExpandedRunnable);
3763             }
3764         }
3765     };
3766 
3767     /**
3768      * Initialize objects instead of injecting to avoid circular dependencies.
3769      *
3770      * @param hideExpandedRunnable a runnable to run when we need to hide the expanded panel.
3771      */
initDependencies( StatusBar statusBar, Runnable hideExpandedRunnable, NotificationShelfController notificationShelfController)3772     public void initDependencies(
3773             StatusBar statusBar,
3774             Runnable hideExpandedRunnable,
3775             NotificationShelfController notificationShelfController) {
3776         setStatusBar(statusBar);
3777         mHideExpandedRunnable = hideExpandedRunnable;
3778         mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
3779         mNotificationShelfController = notificationShelfController;
3780         mLockscreenShadeTransitionController.bindController(notificationShelfController);
3781         updateMaxDisplayedNotifications(true);
3782     }
3783 
setAlpha(float alpha)3784     public void setAlpha(float alpha) {
3785         mView.setAlpha(alpha);
3786     }
3787 
fadeOut(long startDelayMs, long durationMs, Runnable endAction)3788     public ViewPropertyAnimator fadeOut(long startDelayMs, long durationMs, Runnable endAction) {
3789         return mView.animate().alpha(0).setStartDelay(startDelayMs).setDuration(
3790                 durationMs).setInterpolator(Interpolators.ALPHA_OUT).withLayer().withEndAction(
3791                 endAction);
3792     }
3793 
resetViewGroupFade()3794     public void resetViewGroupFade() {
3795         ViewGroupFadeHelper.reset(mView);
3796     }
3797 
addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener)3798     public void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
3799         mView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
3800     }
3801 
removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener)3802     public void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
3803         mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
3804     }
3805 
getOnHeadsUpChangedListener()3806     public MyOnHeadsUpChangedListener getOnHeadsUpChangedListener() {
3807         return mOnHeadsUpChangedListener;
3808     }
3809 
getHeight()3810     public int getHeight() {
3811         return mView.getHeight();
3812     }
3813 
setHeaderDebugInfo(String text)3814     public void setHeaderDebugInfo(String text) {
3815         if (DEBUG) mHeaderDebugInfo = text;
3816     }
3817 
onThemeChanged()3818     public void onThemeChanged() {
3819         mConfigurationListener.onThemeChanged();
3820     }
3821 
3822     @Override
createLayoutChangeListener()3823     public OnLayoutChangeListener createLayoutChangeListener() {
3824         return new OnLayoutChangeListener();
3825     }
3826 
3827     @Override
createTouchHandler()3828     protected TouchHandler createTouchHandler() {
3829         return new TouchHandler() {
3830 
3831             private long mLastTouchDownTime = -1L;
3832 
3833             @Override
3834             public boolean onInterceptTouchEvent(MotionEvent event) {
3835                 if (mBlockTouches || mQs.disallowPanelTouches()) {
3836                     return false;
3837                 }
3838                 initDownStates(event);
3839                 // Do not let touches go to shade or QS if the bouncer is visible,
3840                 // but still let user swipe down to expand the panel, dismissing the bouncer.
3841                 if (mStatusBar.isBouncerShowing()) {
3842                     return true;
3843                 }
3844                 if (mCommandQueue.panelsEnabled()
3845                         && !mNotificationStackScrollLayoutController.isLongPressInProgress()
3846                         && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
3847                     mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
3848                     mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
3849                     return true;
3850                 }
3851                 if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
3852                         && mPulseExpansionHandler.onInterceptTouchEvent(event)) {
3853                     return true;
3854                 }
3855 
3856                 if (!isFullyCollapsed() && onQsIntercept(event)) {
3857                     return true;
3858                 }
3859                 return super.onInterceptTouchEvent(event);
3860             }
3861 
3862             @Override
3863             public boolean onTouch(View v, MotionEvent event) {
3864                 if (event.getAction() == MotionEvent.ACTION_DOWN) {
3865                     if (event.getDownTime() == mLastTouchDownTime) {
3866                         // An issue can occur when swiping down after unlock, where multiple down
3867                         // events are received in this handler with identical downTimes. Until the
3868                         // source of the issue can be located, detect this case and ignore.
3869                         // see b/193350347
3870                         Log.w(TAG, "Duplicate down event detected... ignoring");
3871                         return true;
3872                     }
3873                     mLastTouchDownTime = event.getDownTime();
3874                 }
3875 
3876 
3877                 if (mBlockTouches || (mQsFullyExpanded && mQs != null
3878                         && mQs.disallowPanelTouches())) {
3879                     return false;
3880                 }
3881 
3882                 // Do not allow panel expansion if bouncer is scrimmed, otherwise user would be able
3883                 // to pull down QS or expand the shade.
3884                 if (mStatusBar.isBouncerShowingScrimmed()) {
3885                     return false;
3886                 }
3887 
3888                 // Make sure the next touch won't the blocked after the current ends.
3889                 if (event.getAction() == MotionEvent.ACTION_UP
3890                         || event.getAction() == MotionEvent.ACTION_CANCEL) {
3891                     mBlockingExpansionForCurrentTouch = false;
3892                 }
3893                 // When touch focus transfer happens, ACTION_DOWN->ACTION_UP may happen immediately
3894                 // without any ACTION_MOVE event.
3895                 // In such case, simply expand the panel instead of being stuck at the bottom bar.
3896                 if (mLastEventSynthesizedDown && event.getAction() == MotionEvent.ACTION_UP) {
3897                     expand(true /* animate */);
3898                 }
3899                 initDownStates(event);
3900 
3901                 // If pulse is expanding already, let's give it the touch. There are situations
3902                 // where the panel starts expanding even though we're also pulsing
3903                 boolean pulseShouldGetTouch = (!mIsExpanding
3904                         && !shouldQuickSettingsIntercept(mDownX, mDownY, 0))
3905                         || mPulseExpansionHandler.isExpanding();
3906                 if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) {
3907                     // We're expanding all the other ones shouldn't get this anymore
3908                     return true;
3909                 }
3910                 if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
3911                         && !mNotificationStackScrollLayoutController.isLongPressInProgress()
3912                         && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
3913                     mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
3914                 }
3915                 boolean handled = false;
3916                 if ((!mIsExpanding || mHintAnimationRunning) && !mQsExpanded
3917                         && mBarState != StatusBarState.SHADE && !mDozing) {
3918                     handled |= mAffordanceHelper.onTouchEvent(event);
3919                 }
3920                 if (mOnlyAffordanceInThisMotion) {
3921                     return true;
3922                 }
3923                 handled |= mHeadsUpTouchHelper.onTouchEvent(event);
3924 
3925                 if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
3926                     return true;
3927                 }
3928                 if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
3929                     mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
3930                     handled = true;
3931                 }
3932 
3933                 if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyExpanded()
3934                         && mStatusBarKeyguardViewManager.isShowing()) {
3935                     mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX());
3936                 }
3937 
3938                 handled |= super.onTouch(v, event);
3939                 return !mDozing || mPulsing || handled;
3940             }
3941         };
3942     }
3943 
3944     private final PhoneStatusBarView.TouchEventHandler mStatusBarViewTouchEventHandler =
3945             new PhoneStatusBarView.TouchEventHandler() {
3946                 @Override
3947                 public void onInterceptTouchEvent(MotionEvent event) {
3948                     mStatusBar.onTouchEvent(event);
3949                 }
3950 
3951                 @Override
3952                 public boolean handleTouchEvent(MotionEvent event) {
3953                     mStatusBar.onTouchEvent(event);
3954 
3955                     // TODO(b/202981994): Move the touch debugging in this method to a central
3956                     //  location. (Right now, it's split between StatusBar and here.)
3957 
3958                     // If panels aren't enabled, ignore the gesture and don't pass it down to the
3959                     // panel view.
3960                     if (!mCommandQueue.panelsEnabled()) {
3961                         if (event.getAction() == MotionEvent.ACTION_DOWN) {
3962                             Log.v(
3963                                     TAG,
3964                                     String.format(
3965                                             "onTouchForwardedFromStatusBar: "
3966                                                     + "panel disabled, ignoring touch at (%d,%d)",
3967                                             (int) event.getX(),
3968                                             (int) event.getY()
3969                                     )
3970                             );
3971                         }
3972                         return false;
3973                     }
3974 
3975                     // If the view that would receive the touch is disabled, just have status bar
3976                     // eat the gesture.
3977                     if (event.getAction() == MotionEvent.ACTION_DOWN && !mView.isEnabled()) {
3978                         Log.v(TAG,
3979                                 String.format(
3980                                         "onTouchForwardedFromStatusBar: "
3981                                                 + "panel view disabled, eating touch at (%d,%d)",
3982                                         (int) event.getX(),
3983                                         (int) event.getY()
3984                                 )
3985                         );
3986                         return true;
3987                     }
3988 
3989                     return mView.dispatchTouchEvent(event);
3990                 }
3991             };
3992 
3993     @Override
3994     protected PanelViewController.OnConfigurationChangedListener
createOnConfigurationChangedListener()3995             createOnConfigurationChangedListener() {
3996         return new OnConfigurationChangedListener();
3997     }
3998 
getNotificationStackScrollLayoutController()3999     public NotificationStackScrollLayoutController getNotificationStackScrollLayoutController() {
4000         return mNotificationStackScrollLayoutController;
4001     }
4002 
4003     /**
4004      * Close the keyguard user switcher if it is open and capable of closing.
4005      *
4006      * Has no effect if user switcher isn't supported, if the user switcher is already closed, or
4007      * if the user switcher uses "simple" mode. The simple user switcher cannot be closed.
4008      *
4009      * @return true if the keyguard user switcher was open, and is now closed
4010      */
closeUserSwitcherIfOpen()4011     public boolean closeUserSwitcherIfOpen() {
4012         if (mKeyguardUserSwitcherController != null) {
4013             return mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(
4014                     true /* animate */);
4015         }
4016         return false;
4017     }
4018 
updateUserSwitcherFlags()4019     private void updateUserSwitcherFlags() {
4020         mKeyguardUserSwitcherEnabled = mResources.getBoolean(
4021                 com.android.internal.R.bool.config_keyguardUserSwitcher);
4022         mKeyguardQsUserSwitchEnabled =
4023                 mKeyguardUserSwitcherEnabled && mResources.getBoolean(
4024                         R.bool.config_keyguard_user_switch_opens_qs_details);
4025     }
4026 
registerSettingsChangeListener()4027     private void registerSettingsChangeListener() {
4028         mContentResolver.registerContentObserver(
4029                 Settings.Global.getUriFor(Settings.Global.USER_SWITCHER_ENABLED),
4030                 /* notifyForDescendants */ false,
4031                 mSettingsChangeObserver
4032         );
4033     }
4034 
unregisterSettingsChangeListener()4035     private void unregisterSettingsChangeListener() {
4036         mContentResolver.unregisterContentObserver(mSettingsChangeObserver);
4037     }
4038 
4039     /**
4040      * Updates notification panel-specific flags on {@link SysUiState}.
4041      */
updateSystemUiStateFlags()4042     public void updateSystemUiStateFlags() {
4043         if (SysUiState.DEBUG) {
4044             Log.d(TAG, "Updating panel sysui state flags: fullyExpanded="
4045                     + isFullyExpanded() + " inQs=" + isInSettings());
4046         }
4047         mSysUiState.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
4048                 isFullyExpanded() && !isInSettings())
4049                 .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED, isInSettings())
4050                 .commitUpdate(mDisplayId);
4051     }
4052 
4053     private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
4054         @Override
onHeightChanged(ExpandableView view, boolean needsAnimation)4055         public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
4056 
4057             // Block update if we are in quick settings and just the top padding changed
4058             // (i.e. view == null).
4059             if (view == null && mQsExpanded) {
4060                 return;
4061             }
4062             if (needsAnimation && mInterpolatedDarkAmount == 0) {
4063                 mAnimateNextPositionUpdate = true;
4064             }
4065             ExpandableView firstChildNotGone =
4066                     mNotificationStackScrollLayoutController.getFirstChildNotGone();
4067             ExpandableNotificationRow
4068                     firstRow =
4069                     firstChildNotGone instanceof ExpandableNotificationRow
4070                             ? (ExpandableNotificationRow) firstChildNotGone : null;
4071             if (firstRow != null && (view == firstRow || (firstRow.getNotificationParent()
4072                     == firstRow))) {
4073                 requestScrollerTopPaddingUpdate(false /* animate */);
4074             }
4075             requestPanelHeightUpdate();
4076         }
4077 
4078         @Override
onReset(ExpandableView view)4079         public void onReset(ExpandableView view) {
4080         }
4081     }
4082 
4083     private class OnClickListener implements View.OnClickListener {
4084         @Override
onClick(View v)4085         public void onClick(View v) {
4086             onQsExpansionStarted();
4087             if (mQsExpanded) {
4088                 flingSettings(0 /* vel */, FLING_COLLAPSE, null /* onFinishRunnable */,
4089                         true /* isClick */);
4090             } else if (isQsExpansionEnabled()) {
4091                 mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
4092                 flingSettings(0 /* vel */, FLING_EXPAND, null /* onFinishRunnable */,
4093                         true /* isClick */);
4094             }
4095         }
4096     }
4097 
4098     private class OnOverscrollTopChangedListener implements
4099             NotificationStackScrollLayout.OnOverscrollTopChangedListener {
4100         @Override
onOverscrollTopChanged(float amount, boolean isRubberbanded)4101         public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
4102             // When in split shade, overscroll shouldn't carry through to QS
4103             if (mShouldUseSplitNotificationShade) {
4104                 return;
4105             }
4106             cancelQsAnimation();
4107             if (!isQsExpansionEnabled()) {
4108                 amount = 0f;
4109             }
4110             float rounded = amount >= 1f ? amount : 0f;
4111             setOverScrolling(rounded != 0f && isRubberbanded);
4112             mQsExpansionFromOverscroll = rounded != 0f;
4113             mLastOverscroll = rounded;
4114             updateQsState();
4115             setQsExpansion(mQsMinExpansionHeight + rounded);
4116         }
4117 
4118         @Override
flingTopOverscroll(float velocity, boolean open)4119         public void flingTopOverscroll(float velocity, boolean open) {
4120             // in split shade mode we want to expand/collapse QS only when touch happens within QS
4121             if (mShouldUseSplitNotificationShade && touchXOutsideOfQs(mInitialTouchX)) {
4122                 return;
4123             }
4124             mLastOverscroll = 0f;
4125             mQsExpansionFromOverscroll = false;
4126             if (open) {
4127                 // During overscrolling, qsExpansion doesn't actually change that the qs is
4128                 // becoming expanded. Any layout could therefore reset the position again. Let's
4129                 // make sure we can expand
4130                 setOverScrolling(false);
4131             }
4132             setQsExpansion(mQsExpansionHeight);
4133             boolean canExpand = isQsExpansionEnabled();
4134             flingSettings(!canExpand && open ? 0f : velocity,
4135                     open && canExpand ? FLING_EXPAND : FLING_COLLAPSE, () -> {
4136                         setOverScrolling(false);
4137                         updateQsState();
4138                     }, false /* isClick */);
4139         }
4140     }
4141 
4142     private class DynamicPrivacyControlListener implements DynamicPrivacyController.Listener {
4143         @Override
onDynamicPrivacyChanged()4144         public void onDynamicPrivacyChanged() {
4145             // Do not request animation when pulsing or waking up, otherwise the clock wiill be out
4146             // of sync with the notification panel.
4147             if (mLinearDarkAmount != 0) {
4148                 return;
4149             }
4150             mAnimateNextPositionUpdate = true;
4151         }
4152     }
4153 
4154     private class KeyguardAffordanceHelperCallback implements KeyguardAffordanceHelper.Callback {
4155         @Override
onAnimationToSideStarted(boolean rightPage, float translation, float vel)4156         public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) {
4157             boolean
4158                     start =
4159                     mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? rightPage
4160                             : !rightPage;
4161             mIsLaunchTransitionRunning = true;
4162             mLaunchAnimationEndRunnable = null;
4163             float displayDensity = mStatusBar.getDisplayDensity();
4164             int lengthDp = Math.abs((int) (translation / displayDensity));
4165             int velocityDp = Math.abs((int) (vel / displayDensity));
4166             if (start) {
4167                 mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_DIALER, lengthDp, velocityDp);
4168                 mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_DIALER);
4169                 mFalsingCollector.onLeftAffordanceOn();
4170                 if (mFalsingCollector.shouldEnforceBouncer()) {
4171                     mStatusBar.executeRunnableDismissingKeyguard(
4172                             () -> mKeyguardBottomArea.launchLeftAffordance(), null,
4173                             true /* dismissShade */, false /* afterKeyguardGone */,
4174                             true /* deferred */);
4175                 } else {
4176                     mKeyguardBottomArea.launchLeftAffordance();
4177                 }
4178             } else {
4179                 if (KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE.equals(
4180                         mLastCameraLaunchSource)) {
4181                     mLockscreenGestureLogger.write(
4182                             MetricsEvent.ACTION_LS_CAMERA, lengthDp, velocityDp);
4183                     mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_CAMERA);
4184                 }
4185                 mFalsingCollector.onCameraOn();
4186                 if (mFalsingCollector.shouldEnforceBouncer()) {
4187                     mStatusBar.executeRunnableDismissingKeyguard(
4188                             () -> mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource), null,
4189                             true /* dismissShade */, false /* afterKeyguardGone */,
4190                             true /* deferred */);
4191                 } else {
4192                     mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource);
4193                 }
4194             }
4195             mStatusBar.startLaunchTransitionTimeout();
4196             mBlockTouches = true;
4197         }
4198 
4199         @Override
onAnimationToSideEnded()4200         public void onAnimationToSideEnded() {
4201             mIsLaunchTransitionRunning = false;
4202             mIsLaunchTransitionFinished = true;
4203             if (mLaunchAnimationEndRunnable != null) {
4204                 mLaunchAnimationEndRunnable.run();
4205                 mLaunchAnimationEndRunnable = null;
4206             }
4207             mStatusBar.readyForKeyguardDone();
4208         }
4209 
4210         @Override
getMaxTranslationDistance()4211         public float getMaxTranslationDistance() {
4212             return (float) Math.hypot(mView.getWidth(), getHeight());
4213         }
4214 
4215         @Override
onSwipingStarted(boolean rightIcon)4216         public void onSwipingStarted(boolean rightIcon) {
4217             mFalsingCollector.onAffordanceSwipingStarted(rightIcon);
4218             boolean
4219                     camera =
4220                     mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? !rightIcon
4221                             : rightIcon;
4222             if (camera) {
4223                 mKeyguardBottomArea.bindCameraPrewarmService();
4224             }
4225             mView.requestDisallowInterceptTouchEvent(true);
4226             mOnlyAffordanceInThisMotion = true;
4227             mQsTracking = false;
4228         }
4229 
4230         @Override
onSwipingAborted()4231         public void onSwipingAborted() {
4232             mFalsingCollector.onAffordanceSwipingAborted();
4233             mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */);
4234         }
4235 
4236         @Override
onIconClicked(boolean rightIcon)4237         public void onIconClicked(boolean rightIcon) {
4238             if (mHintAnimationRunning) {
4239                 return;
4240             }
4241             mHintAnimationRunning = true;
4242             mAffordanceHelper.startHintAnimation(rightIcon, () -> {
4243                 mHintAnimationRunning = false;
4244                 mStatusBar.onHintFinished();
4245             });
4246             rightIcon =
4247                     mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? !rightIcon
4248                             : rightIcon;
4249             if (rightIcon) {
4250                 mStatusBar.onCameraHintStarted();
4251             } else {
4252                 if (mKeyguardBottomArea.isLeftVoiceAssist()) {
4253                     mStatusBar.onVoiceAssistHintStarted();
4254                 } else {
4255                     mStatusBar.onPhoneHintStarted();
4256                 }
4257             }
4258         }
4259 
4260         @Override
getLeftIcon()4261         public KeyguardAffordanceView getLeftIcon() {
4262             return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
4263                     ? mKeyguardBottomArea.getRightView() : mKeyguardBottomArea.getLeftView();
4264         }
4265 
4266         @Override
getRightIcon()4267         public KeyguardAffordanceView getRightIcon() {
4268             return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
4269                     ? mKeyguardBottomArea.getLeftView() : mKeyguardBottomArea.getRightView();
4270         }
4271 
4272         @Override
getLeftPreview()4273         public View getLeftPreview() {
4274             return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
4275                     ? mKeyguardBottomArea.getRightPreview() : mKeyguardBottomArea.getLeftPreview();
4276         }
4277 
4278         @Override
getRightPreview()4279         public View getRightPreview() {
4280             return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
4281                     ? mKeyguardBottomArea.getLeftPreview() : mKeyguardBottomArea.getRightPreview();
4282         }
4283 
4284         @Override
getAffordanceFalsingFactor()4285         public float getAffordanceFalsingFactor() {
4286             return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
4287         }
4288 
4289         @Override
needsAntiFalsing()4290         public boolean needsAntiFalsing() {
4291             return mBarState == KEYGUARD;
4292         }
4293     }
4294 
4295     private class OnEmptySpaceClickListener implements
4296             NotificationStackScrollLayout.OnEmptySpaceClickListener {
4297         @Override
onEmptySpaceClicked(float x, float y)4298         public void onEmptySpaceClicked(float x, float y) {
4299             onEmptySpaceClick(x);
4300         }
4301     }
4302 
4303     private class MyOnHeadsUpChangedListener implements OnHeadsUpChangedListener {
4304         @Override
onHeadsUpPinnedModeChanged(final boolean inPinnedMode)4305         public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
4306             if (inPinnedMode) {
4307                 mHeadsUpExistenceChangedRunnable.run();
4308                 updateNotificationTranslucency();
4309             } else {
4310                 setHeadsUpAnimatingAway(true);
4311                 mNotificationStackScrollLayoutController.runAfterAnimationFinished(
4312                         mHeadsUpExistenceChangedRunnable);
4313             }
4314             updateGestureExclusionRect();
4315             mHeadsUpPinnedMode = inPinnedMode;
4316             updateVisibility();
4317             mKeyguardStatusBarViewController.updateForHeadsUp();
4318         }
4319 
4320         @Override
onHeadsUpPinned(NotificationEntry entry)4321         public void onHeadsUpPinned(NotificationEntry entry) {
4322             if (!isOnKeyguard()) {
4323                 mNotificationStackScrollLayoutController.generateHeadsUpAnimation(
4324                         entry.getHeadsUpAnimationView(), true);
4325             }
4326         }
4327 
4328         @Override
onHeadsUpUnPinned(NotificationEntry entry)4329         public void onHeadsUpUnPinned(NotificationEntry entry) {
4330 
4331             // When we're unpinning the notification via active edge they remain heads-upped,
4332             // we need to make sure that an animation happens in this case, otherwise the
4333             // notification
4334             // will stick to the top without any interaction.
4335             if (isFullyCollapsed() && entry.isRowHeadsUp() && !isOnKeyguard()) {
4336                 mNotificationStackScrollLayoutController.generateHeadsUpAnimation(
4337                         entry.getHeadsUpAnimationView(), false);
4338                 entry.setHeadsUpIsVisible();
4339             }
4340         }
4341     }
4342 
4343     private class HeightListener implements QS.HeightListener {
onQsHeightChanged()4344         public void onQsHeightChanged() {
4345             mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0;
4346             if (mQsExpanded && mQsFullyExpanded) {
4347                 mQsExpansionHeight = mQsMaxExpansionHeight;
4348                 requestScrollerTopPaddingUpdate(false /* animate */);
4349                 requestPanelHeightUpdate();
4350             }
4351             if (mAccessibilityManager.isEnabled()) {
4352                 mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
4353             }
4354             mNotificationStackScrollLayoutController.setMaxTopPadding(mQsMaxExpansionHeight);
4355         }
4356     }
4357 
4358     private class ConfigurationListener implements ConfigurationController.ConfigurationListener {
4359         @Override
onThemeChanged()4360         public void onThemeChanged() {
4361             if (DEBUG) Log.d(TAG, "onThemeChanged");
4362             mThemeResId = mView.getContext().getThemeResId();
4363             reInflateViews();
4364         }
4365 
4366         @Override
onSmallestScreenWidthChanged()4367         public void onSmallestScreenWidthChanged() {
4368             if (DEBUG) Log.d(TAG, "onSmallestScreenWidthChanged");
4369 
4370             // Can affect multi-user switcher visibility as it depends on screen size by default:
4371             // it is enabled only for devices with large screens (see config_keyguardUserSwitcher)
4372             reInflateViews();
4373         }
4374 
4375         @Override
onDensityOrFontScaleChanged()4376         public void onDensityOrFontScaleChanged() {
4377             if (DEBUG) Log.d(TAG, "onDensityOrFontScaleChanged");
4378             reInflateViews();
4379         }
4380     }
4381 
4382     private class SettingsChangeObserver extends ContentObserver {
4383 
SettingsChangeObserver(Handler handler)4384         SettingsChangeObserver(Handler handler) {
4385             super(handler);
4386         }
4387 
4388         @Override
onChange(boolean selfChange)4389         public void onChange(boolean selfChange) {
4390             if (DEBUG) Log.d(TAG, "onSettingsChanged");
4391 
4392             // Can affect multi-user switcher visibility
4393             reInflateViews();
4394         }
4395     }
4396 
4397     private class StatusBarStateListener implements StateListener {
4398         @Override
onStateChanged(int statusBarState)4399         public void onStateChanged(int statusBarState) {
4400             boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
4401             boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
4402             int oldState = mBarState;
4403             boolean keyguardShowing = statusBarState == KEYGUARD;
4404 
4405             if (mDozeParameters.shouldControlUnlockedScreenOff()
4406                     && oldState == StatusBarState.SHADE
4407                     && statusBarState == KEYGUARD) {
4408                 // This means we're doing the screen off animation - position the keyguard status
4409                 // view where it'll be on AOD, so we can animate it in.
4410                 mKeyguardStatusViewController.updatePosition(
4411                         mClockPositionResult.clockX,
4412                         mClockPositionResult.clockYFullyDozing,
4413                         mClockPositionResult.clockScale,
4414                         false /* animate */);
4415             }
4416 
4417             mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
4418                     statusBarState,
4419                     keyguardFadingAway,
4420                     goingToFullShade,
4421                     mBarState);
4422             setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
4423 
4424             mBarState = statusBarState;
4425             mKeyguardShowing = keyguardShowing;
4426 
4427             if (oldState == KEYGUARD && (goingToFullShade
4428                     || statusBarState == StatusBarState.SHADE_LOCKED)) {
4429 
4430                 long startDelay;
4431                 long duration;
4432                 if (mKeyguardStateController.isKeyguardFadingAway()) {
4433                     startDelay = mKeyguardStateController.getKeyguardFadingAwayDelay();
4434                     duration = mKeyguardStateController.getShortenedFadingAwayDuration();
4435                 } else {
4436                     startDelay = 0;
4437                     duration = StackStateAnimator.ANIMATION_DURATION_STANDARD;
4438                 }
4439                 mKeyguardStatusBarViewController.animateKeyguardStatusBarOut(startDelay, duration);
4440                 updateQSMinHeight();
4441             } else if (oldState == StatusBarState.SHADE_LOCKED
4442                     && statusBarState == KEYGUARD) {
4443                 mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
4444 
4445                 mNotificationStackScrollLayoutController.resetScrollPosition();
4446                 // Only animate header if the header is visible. If not, it will partially
4447                 // animate out
4448                 // the top of QS
4449                 if (!mQsExpanded) {
4450                     // TODO(b/185683835) Nicer clipping when using new spacial model
4451                     if (mShouldUseSplitNotificationShade) {
4452                         mQs.animateHeaderSlidingOut();
4453                     }
4454                 }
4455             } else {
4456                 final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE
4457                         && statusBarState == KEYGUARD
4458                         && mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying();
4459                 if (!animatingUnlockedShadeToKeyguard) {
4460                     // Only make the status bar visible if we're not animating the screen off, since
4461                     // we only want to be showing the clock/notifications during the animation.
4462                     mKeyguardStatusBarViewController.updateViewState(
4463                             /* alpha= */ 1f,
4464                             keyguardShowing ? View.VISIBLE : View.INVISIBLE);
4465                 }
4466                 if (keyguardShowing && oldState != mBarState) {
4467                     if (mQs != null) {
4468                         mQs.hideImmediately();
4469                     }
4470                 }
4471             }
4472             mKeyguardStatusBarViewController.updateForHeadsUp();
4473             if (keyguardShowing) {
4474                 updateDozingVisibilities(false /* animate */);
4475             }
4476 
4477             updateMaxDisplayedNotifications(false);
4478             // The update needs to happen after the headerSlide in above, otherwise the translation
4479             // would reset
4480             maybeAnimateBottomAreaAlpha();
4481             updateQsState();
4482             mSplitShadeHeaderController.setShadeExpanded(
4483                     mBarState == SHADE || mBarState == SHADE_LOCKED);
4484         }
4485 
4486         @Override
onDozeAmountChanged(float linearAmount, float amount)4487         public void onDozeAmountChanged(float linearAmount, float amount) {
4488             mInterpolatedDarkAmount = amount;
4489             mLinearDarkAmount = linearAmount;
4490             mKeyguardStatusViewController.setDarkAmount(mInterpolatedDarkAmount);
4491             mKeyguardBottomArea.setDarkAmount(mInterpolatedDarkAmount);
4492             positionClockAndNotifications();
4493         }
4494     }
4495 
4496     /**
4497      * An interface that provides the current state of the notification panel and related views,
4498      * which is needed to calculate {@link KeyguardStatusBarView}'s state in
4499      * {@link KeyguardStatusBarViewController}.
4500      */
4501     public interface NotificationPanelViewStateProvider {
4502         /** Returns the expanded height of the panel view. */
getPanelViewExpandedHeight()4503         float getPanelViewExpandedHeight();
4504         /** Returns the fraction of QS that's expanded. */
getQsExpansionFraction()4505         float getQsExpansionFraction();
4506         /**
4507          * Returns true if heads up should be visible.
4508          *
4509          * TODO(b/138786270): If HeadsUpAppearanceController was injectable, we could inject it into
4510          * {@link KeyguardStatusBarViewController} and remove this method.
4511          */
shouldHeadsUpBeVisible()4512         boolean shouldHeadsUpBeVisible();
4513     }
4514 
4515     private final NotificationPanelViewStateProvider mNotificationPanelViewStateProvider =
4516             new NotificationPanelViewStateProvider() {
4517                 @Override
4518                 public float getPanelViewExpandedHeight() {
4519                     return getExpandedHeight();
4520                 }
4521 
4522                 @Override
4523                 public float getQsExpansionFraction() {
4524                     return computeQsExpansionFraction();
4525                 }
4526 
4527                 @Override
4528                 public boolean shouldHeadsUpBeVisible() {
4529                     return mHeadsUpAppearanceController.shouldBeVisible();
4530                 }
4531             };
4532 
4533     /**
4534      * Reconfigures the shade to show the AOD UI (clock, smartspace, etc). This is called by the
4535      * screen off animation controller in order to animate in AOD without "actually" fully switching
4536      * to the KEYGUARD state, which is a heavy transition that causes jank as 10+ files react to the
4537      * change.
4538      */
showAodUi()4539     public void showAodUi() {
4540         setDozing(true /* dozing */, false /* animate */, null);
4541         mStatusBarStateController.setUpcomingState(KEYGUARD);
4542         mEntryManager.updateNotifications("showAodUi");
4543         mStatusBarStateListener.onStateChanged(KEYGUARD);
4544         mStatusBarStateListener.onDozeAmountChanged(1f, 1f);
4545         setExpandedFraction(1f);
4546     }
4547 
4548     /**
4549      * Sets the overstretch amount in raw pixels when dragging down.
4550      */
setOverStrechAmount(float amount)4551     public void setOverStrechAmount(float amount) {
4552         float progress = amount / mView.getHeight();
4553         float overstretch = Interpolators.getOvershootInterpolation(progress);
4554         mOverStretchAmount = overstretch * mMaxOverscrollAmountForPulse;
4555         positionClockAndNotifications(true /* forceUpdate */);
4556     }
4557 
4558     private class OnAttachStateChangeListener implements View.OnAttachStateChangeListener {
4559         @Override
onViewAttachedToWindow(View v)4560         public void onViewAttachedToWindow(View v) {
4561             mFragmentService.getFragmentHostManager(mView)
4562                             .addTagListener(QS.TAG, mFragmentListener);
4563             mStatusBarStateController.addCallback(mStatusBarStateListener);
4564             mConfigurationController.addCallback(mConfigurationListener);
4565             // Theme might have changed between inflating this view and attaching it to the
4566             // window, so
4567             // force a call to onThemeChanged
4568             mConfigurationListener.onThemeChanged();
4569             mFalsingManager.addTapListener(mFalsingTapListener);
4570             mKeyguardIndicationController.init();
4571             registerSettingsChangeListener();
4572         }
4573 
4574         @Override
onViewDetachedFromWindow(View v)4575         public void onViewDetachedFromWindow(View v) {
4576             unregisterSettingsChangeListener();
4577             mFragmentService.getFragmentHostManager(mView)
4578                             .removeTagListener(QS.TAG, mFragmentListener);
4579             mStatusBarStateController.removeCallback(mStatusBarStateListener);
4580             mConfigurationController.removeCallback(mConfigurationListener);
4581             mFalsingManager.removeTapListener(mFalsingTapListener);
4582         }
4583     }
4584 
4585     private class OnLayoutChangeListener extends PanelViewController.OnLayoutChangeListener {
4586 
4587         @Override
onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)4588         public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
4589                 int oldTop, int oldRight, int oldBottom) {
4590             DejankUtils.startDetectingBlockingIpcs("NVP#onLayout");
4591             super.onLayoutChange(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom);
4592             updateMaxDisplayedNotifications(true);
4593             setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth());
4594 
4595             // Update Clock Pivot
4596             mKeyguardStatusViewController.setPivotX(mView.getWidth() / 2);
4597             mKeyguardStatusViewController.setPivotY(
4598                     (FONT_HEIGHT - CAP_HEIGHT) / 2048f
4599                             * mKeyguardStatusViewController.getClockTextSize());
4600 
4601             // Calculate quick setting heights.
4602             int oldMaxHeight = mQsMaxExpansionHeight;
4603             if (mQs != null) {
4604                 updateQSMinHeight();
4605                 mQsMaxExpansionHeight = mQs.getDesiredHeight();
4606                 mNotificationStackScrollLayoutController.setMaxTopPadding(mQsMaxExpansionHeight);
4607             }
4608             positionClockAndNotifications();
4609             if (mQsExpanded && mQsFullyExpanded) {
4610                 mQsExpansionHeight = mQsMaxExpansionHeight;
4611                 requestScrollerTopPaddingUpdate(false /* animate */);
4612                 requestPanelHeightUpdate();
4613 
4614                 // Size has changed, start an animation.
4615                 if (mQsMaxExpansionHeight != oldMaxHeight) {
4616                     startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight);
4617                 }
4618             } else if (!mQsExpanded && mQsExpansionAnimator == null) {
4619                 setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
4620             }
4621             updateExpandedHeight(getExpandedHeight());
4622             updateHeader();
4623 
4624             // If we are running a size change animation, the animation takes care of the height of
4625             // the container. However, if we are not animating, we always need to make the QS
4626             // container
4627             // the desired height so when closing the QS detail, it stays smaller after the size
4628             // change
4629             // animation is finished but the detail view is still being animated away (this
4630             // animation
4631             // takes longer than the size change animation).
4632             if (mQsSizeChangeAnimator == null && mQs != null) {
4633                 mQs.setHeightOverride(mQs.getDesiredHeight());
4634             }
4635             updateMaxHeadsUpTranslation();
4636             updateGestureExclusionRect();
4637             if (mExpandAfterLayoutRunnable != null) {
4638                 mExpandAfterLayoutRunnable.run();
4639                 mExpandAfterLayoutRunnable = null;
4640             }
4641             DejankUtils.stopDetectingBlockingIpcs("NVP#onLayout");
4642         }
4643     }
4644 
updateQSMinHeight()4645     private void updateQSMinHeight() {
4646         float previousMin = mQsMinExpansionHeight;
4647         mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight();
4648         if (mQsExpansionHeight == previousMin) {
4649             mQsExpansionHeight = mQsMinExpansionHeight;
4650         }
4651     }
4652 
4653     private class DebugDrawable extends Drawable {
4654 
4655         @Override
draw(Canvas canvas)4656         public void draw(Canvas canvas) {
4657             Paint p = new Paint();
4658             p.setColor(Color.RED);
4659             p.setStrokeWidth(2);
4660             p.setStyle(Paint.Style.STROKE);
4661             canvas.drawLine(0, getMaxPanelHeight(), mView.getWidth(), getMaxPanelHeight(), p);
4662             p.setTextSize(24);
4663             if (mHeaderDebugInfo != null) canvas.drawText(mHeaderDebugInfo, 50, 100, p);
4664             p.setColor(Color.BLUE);
4665             canvas.drawLine(0, getExpandedHeight(), mView.getWidth(), getExpandedHeight(), p);
4666             p.setColor(Color.GREEN);
4667             canvas.drawLine(0, calculatePanelHeightQsExpanded(), mView.getWidth(),
4668                     calculatePanelHeightQsExpanded(), p);
4669             p.setColor(Color.YELLOW);
4670             canvas.drawLine(0, calculatePanelHeightShade(), mView.getWidth(),
4671                     calculatePanelHeightShade(), p);
4672             p.setColor(Color.MAGENTA);
4673             canvas.drawLine(
4674                     0, calculateNotificationsTopPadding(), mView.getWidth(),
4675                     calculateNotificationsTopPadding(), p);
4676             p.setColor(Color.CYAN);
4677             canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, mView.getWidth(),
4678                     mNotificationStackScrollLayoutController.getTopPadding(), p);
4679             p.setColor(Color.GRAY);
4680             canvas.drawLine(0, mClockPositionResult.clockY, mView.getWidth(),
4681                     mClockPositionResult.clockY, p);
4682         }
4683 
4684         @Override
setAlpha(int alpha)4685         public void setAlpha(int alpha) {
4686 
4687         }
4688 
4689         @Override
setColorFilter(ColorFilter colorFilter)4690         public void setColorFilter(ColorFilter colorFilter) {
4691 
4692         }
4693 
4694         @Override
getOpacity()4695         public int getOpacity() {
4696             return 0;
4697         }
4698     }
4699 
4700     private class OnConfigurationChangedListener extends
4701             PanelViewController.OnConfigurationChangedListener {
4702         @Override
onConfigurationChanged(Configuration newConfig)4703         public void onConfigurationChanged(Configuration newConfig) {
4704             super.onConfigurationChanged(newConfig);
4705             mAffordanceHelper.onConfigurationChanged();
4706             mLastOrientation = newConfig.orientation;
4707         }
4708     }
4709 
4710     private class OnApplyWindowInsetsListener implements View.OnApplyWindowInsetsListener {
onApplyWindowInsets(View v, WindowInsets insets)4711         public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
4712             // the same types of insets that are handled in NotificationShadeWindowView
4713             int insetTypes = WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout();
4714             Insets combinedInsets = insets.getInsetsIgnoringVisibility(insetTypes);
4715             mDisplayTopInset = combinedInsets.top;
4716             mDisplayRightInset = combinedInsets.right;
4717 
4718             mNavigationBarBottomHeight = insets.getStableInsetBottom();
4719             updateMaxHeadsUpTranslation();
4720             return insets;
4721         }
4722     }
4723 
4724     /** Removes any pending runnables that would collapse the panel. */
cancelPendingPanelCollapse()4725     public void cancelPendingPanelCollapse() {
4726         mView.removeCallbacks(mMaybeHideExpandedRunnable);
4727     }
4728 
4729     @PanelState
4730     private int mCurrentPanelState = STATE_CLOSED;
4731 
onPanelStateChanged(@anelState int state)4732     private void onPanelStateChanged(@PanelState int state) {
4733         mAmbientState.setIsShadeOpening(state == STATE_OPENING);
4734         updateQSExpansionEnabledAmbient();
4735 
4736         if (state == STATE_OPEN && mCurrentPanelState != state) {
4737             mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
4738         }
4739         if (state == STATE_OPENING) {
4740             mStatusBar.makeExpandedVisible(false);
4741         }
4742         if (state == STATE_CLOSED) {
4743             // Close the status bar in the next frame so we can show the end of the
4744             // animation.
4745             mView.post(mMaybeHideExpandedRunnable);
4746         }
4747         mCurrentPanelState = state;
4748     }
4749 
4750     /** Returns the handler that the status bar should forward touches to. */
getStatusBarTouchEventHandler()4751     public PhoneStatusBarView.TouchEventHandler getStatusBarTouchEventHandler() {
4752         return mStatusBarViewTouchEventHandler;
4753     }
4754 }
4755