diff options
36 files changed, 1655 insertions, 550 deletions
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java index 0a052df43ead..044b5ed16b68 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java @@ -16,6 +16,7 @@ package com.android.systemui.animation; +import android.util.MathUtils; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateInterpolator; import android.view.animation.BounceInterpolator; @@ -72,6 +73,27 @@ public class Interpolators { new PathInterpolator(0.9f, 0f, 0.7f, 1f); /** + * Calculate the amount of overshoot using an exponential falloff function with desired + * properties, where the overshoot smoothly transitions at the 1.0f boundary into the + * overshoot, retaining its acceleration. + * + * @param progress a progress value going from 0 to 1 + * @param overshootAmount the amount > 0 of overshoot desired. A value of 0.1 means the max + * value of the overall progress will be at 1.1. + * @param overshootStart the point in (0,1] where the result should reach 1 + * @return the interpolated overshoot + */ + public static float getOvershootInterpolation(float progress, float overshootAmount, + float overshootStart) { + if (overshootAmount == 0.0f || overshootStart == 0.0f) { + throw new IllegalArgumentException("Invalid values for overshoot"); + } + float b = MathUtils.log((overshootAmount + 1) / (overshootAmount)) / overshootStart; + return MathUtils.max(0.0f, + (float) (1.0f - Math.exp(-b * progress)) * (overshootAmount + 1.0f)); + } + + /** * Interpolate alpha for notifications background scrim during shade expansion. * @param fraction Shade expansion fraction */ diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java index 4d4c909d2894..98ef9e20ac3b 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java @@ -33,7 +33,7 @@ public interface QS extends FragmentBase { String ACTION = "com.android.systemui.action.PLUGIN_QS"; - int VERSION = 8; + int VERSION = 9; String TAG = "QS"; @@ -50,8 +50,13 @@ public interface QS extends FragmentBase { void setListening(boolean listening); boolean isShowingDetail(); void closeDetail(); - default void setShowCollapsedOnKeyguard(boolean showCollapsedOnKeyguard) {} - void animateHeaderSlidingIn(long delay); + + /** + * Set that we're currently pulse expanding + * + * @param pulseExpanding if we're currently expanding during pulsing + */ + default void setPulseExpanding(boolean pulseExpanding) {} void animateHeaderSlidingOut(); void setQsExpansion(float qsExpansionFraction, float headerTranslation); void setHeaderListening(boolean listening); @@ -79,10 +84,23 @@ public interface QS extends FragmentBase { void setTranslateWhileExpanding(boolean shouldTranslate); /** + * Set the amount of pixels we have currently dragged down if we're transitioning to the full + * shade. 0.0f means we're not transitioning yet. + */ + default void setTransitionToFullShadeAmount(float pxAmount, boolean animated) {} + + /** * A rounded corner clipping that makes QS feel as if it were behind everything. */ void setFancyClipping(int top, int bottom, int cornerRadius, boolean visible); + /** + * @return if quick settings is fully collapsed currently + */ + default boolean isFullyCollapsed() { + return true; + } + @ProvidesInterface(version = HeightListener.VERSION) interface HeightListener { int VERSION = 1; diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index 95483f13ec6a..0dc147324008 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -21,8 +21,7 @@ android:id="@+id/keyguard_bottom_area" android:layout_height="match_parent" android:layout_width="match_parent" - android:outlineProvider="none" - android:elevation="5dp" > <!-- Put it above the status bar header --> + android:outlineProvider="none" > <!-- Put it above the status bar header --> <LinearLayout android:id="@+id/keyguard_indication_area" @@ -58,12 +57,6 @@ </LinearLayout> - <FrameLayout - android:id="@+id/preview_container" - android:layout_width="match_parent" - android:layout_height="match_parent"> - </FrameLayout> - <com.android.systemui.statusbar.KeyguardAffordanceView android:id="@+id/camera_button" android:layout_height="@dimen/keyguard_affordance_height" diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index f4cb3b144239..3543fd11d64a 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -37,6 +37,25 @@ android:layout_height="match_parent" android:layout_width="match_parent" /> + <include + layout="@layout/keyguard_bottom_area" + android:visibility="gone" /> + + <ViewStub + android:id="@+id/keyguard_user_switcher_stub" + android:layout="@layout/keyguard_user_switcher" + android:layout_height="match_parent" + android:layout_width="match_parent" /> + + <include layout="@layout/status_bar_expanded_plugin_frame"/> + + <include layout="@layout/dock_info_bottom_area_overlay" /> + + <com.android.keyguard.LockIconView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/lock_icon_view" /> + <com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer android:layout_width="match_parent" android:layout_height="match_parent" @@ -49,6 +68,18 @@ layout="@layout/keyguard_status_view" android:visibility="gone"/> + <com.android.systemui.scrim.ScrimView + android:id="@+id/scrim_notifications" + android:layout_width="0dp" + android:layout_height="0dp" + android:importantForAccessibility="no" + systemui:ignoreRightInset="true" + systemui:layout_constraintStart_toStartOf="parent" + systemui:layout_constraintEnd_toEndOf="parent" + systemui:layout_constraintTop_toTopOf="parent" + systemui:layout_constraintBottom_toBottomOf="parent" + /> + <include layout="@layout/dock_info_overlay" /> <FrameLayout @@ -101,22 +132,9 @@ </com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer> - <include layout="@layout/dock_info_bottom_area_overlay" /> - - <include - layout="@layout/keyguard_bottom_area" - android:visibility="gone" /> - - <ViewStub - android:id="@+id/keyguard_user_switcher_stub" - android:layout="@layout/keyguard_user_switcher" - android:layout_height="match_parent" - android:layout_width="match_parent" /> - - <include layout="@layout/status_bar_expanded_plugin_frame"/> - - <com.android.keyguard.LockIconView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:id="@+id/lock_icon_view" /> + <FrameLayout + android:id="@+id/preview_container" + android:layout_width="match_parent" + android:layout_height="match_parent"> + </FrameLayout> </com.android.systemui.statusbar.phone.NotificationPanelView> diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index bea50e87a29a..08284a0a2e3a 100644 --- a/packages/SystemUI/res/layout/super_notification_shade.xml +++ b/packages/SystemUI/res/layout/super_notification_shade.xml @@ -51,14 +51,6 @@ sysui:ignoreRightInset="true" /> - <com.android.systemui.scrim.ScrimView - android:id="@+id/scrim_notifications" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:importantForAccessibility="no" - sysui:ignoreRightInset="true" - /> - <com.android.systemui.statusbar.LightRevealScrim android:id="@+id/light_reveal_scrim" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 87a669f710db..e7d714e60076 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1409,6 +1409,26 @@ <dimen name="media_output_dialog_icon_corner_radius">16dp</dimen> <dimen name="media_output_dialog_title_anim_y_delta">12.5dp</dimen> + <!-- Delay after which the media will start transitioning to the full shade on + the lockscreen --> + <dimen name="lockscreen_shade_media_transition_start_delay">40dp</dimen> + + <!-- Distance that the full shade transition takes in order for qs to fully transition to the + shade --> + <dimen name="lockscreen_shade_qs_transition_distance">200dp</dimen> + + <!-- Distance that the full shade transition takes in order for scrim to fully transition to + the shade (in alpha) --> + <dimen name="lockscreen_shade_scrim_transition_distance">80dp</dimen> + + <!-- Extra inset for the notifications when accounting for media during the lockscreen to + shade transition to compensate for the disappearing media --> + <dimen name="lockscreen_shade_transition_extra_media_inset">-48dp</dimen> + + <!-- Maximum overshoot for the topPadding of notifications when transitioning to the full + shade --> + <dimen name="lockscreen_shade_max_top_overshoot">32dp</dimen> + <dimen name="people_space_widget_radius">28dp</dimen> <dimen name="people_space_image_radius">20dp</dimen> <dimen name="people_space_messages_count_radius">12dp</dimen> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 3a3f2fc40b25..388c085fd1a0 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -198,6 +198,13 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } /** + * @return {@code true} if we are currently animating the screen off from unlock + */ + public boolean isAnimatingScreenOffFromUnlocked() { + return mKeyguardVisibilityHelper.isAnimatingScreenOffFromUnlocked(); + } + + /** * Set the visibility of the keyguard status view based on some new state. */ public void setKeyguardStatusViewVisibility( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index 6aca5a702a1a..b6a58dc7cb91 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -22,6 +22,9 @@ import android.view.View; import com.android.systemui.animation.Interpolators; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.notification.AnimatableProperty; +import com.android.systemui.statusbar.notification.PropertyAnimator; +import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -37,6 +40,8 @@ public class KeyguardVisibilityHelper { private final DozeParameters mDozeParameters; private boolean mKeyguardViewVisibilityAnimating; private boolean mLastOccludedState = false; + private boolean mAnimatingScreenOff; + private final AnimationProperties mAnimationProperties = new AnimationProperties(); public KeyguardVisibilityHelper(View view, KeyguardStateController keyguardStateController, DozeParameters dozeParameters) { @@ -89,12 +94,21 @@ public class KeyguardVisibilityHelper { } else if (statusBarState == KEYGUARD) { if (keyguardFadingAway) { mKeyguardViewVisibilityAnimating = true; + float target = mView.getY() - mView.getHeight() * 0.05f; + int delay = 0; + int duration = 125; + // We animate the Y properly separately using the PropertyAnimator, as the panel + // view als needs to update the end position. + mAnimationProperties.setDuration(duration).setDelay(delay); + PropertyAnimator.cancelAnimation(mView, AnimatableProperty.Y); + PropertyAnimator.setProperty(mView, AnimatableProperty.Y, target, + mAnimationProperties, + true /* animate */); mView.animate() .alpha(0) - .translationYBy(-mView.getHeight() * 0.05f) .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN) - .setDuration(125) - .setStartDelay(0) + .setDuration(duration) + .setStartDelay(delay) .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable) .start(); } else if (mLastOccludedState && !isOccluded) { @@ -110,20 +124,30 @@ public class KeyguardVisibilityHelper { .start(); } else if (mDozeParameters.shouldControlUnlockedScreenOff()) { mKeyguardViewVisibilityAnimating = true; + mAnimatingScreenOff = true; mView.setVisibility(View.VISIBLE); mView.setAlpha(0f); + float currentY = mView.getY(); + mView.setY(currentY - mView.getHeight() * 0.1f); + int duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP; + int delay = (int) (duration * .6f); + // We animate the Y properly separately using the PropertyAnimator, as the panel + // view als needs to update the end position. + mAnimationProperties.setDuration(duration).setDelay(delay); + PropertyAnimator.cancelAnimation(mView, AnimatableProperty.Y); + PropertyAnimator.setProperty(mView, AnimatableProperty.Y, currentY, + mAnimationProperties, + true /* animate */); - float curTranslationY = mView.getTranslationY(); - mView.setTranslationY(curTranslationY - mView.getHeight() * 0.1f); mView.animate() - .setStartDelay((int) (StackStateAnimator.ANIMATION_DURATION_WAKEUP * .6f)) - .setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP) + .setStartDelay(delay) + .setDuration(duration) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .alpha(1f) - .translationY(curTranslationY) .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable) .start(); + } else { mView.setVisibility(View.VISIBLE); mView.setAlpha(1f); @@ -148,5 +172,13 @@ public class KeyguardVisibilityHelper { private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> { mKeyguardViewVisibilityAnimating = false; + mAnimatingScreenOff = false; }; + + /** + * @return {@code true} if we are currently animating the screen off from unlock + */ + public boolean isAnimatingScreenOffFromUnlocked() { + return mAnimatingScreenOff; + } } diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index b367bdf08886..7127444befb7 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -145,7 +145,13 @@ public class LockIconViewController extends ViewController<LockIconView> impleme final boolean hasUdfps = mAuthController.getUdfpsSensorLocation() != null; mHasUdfpsOrFaceAuthFeatures = hasFaceAuth || hasUdfps; if (!mHasUdfpsOrFaceAuthFeatures) { - ((ViewGroup) mView.getParent()).removeView(mView); + // Posting since removing a view in the middle of onAttach can lead to a crash in the + // iteration loop when the view isn't last + mView.setVisibility(View.GONE); + mView.post(() -> { + mView.setVisibility(View.VISIBLE); + ((ViewGroup) mView.getParent()).removeView(mView); + }); return; } diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt index b668e881230d..241c2a939f9a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt @@ -64,6 +64,12 @@ class KeyguardMediaController @Inject constructor( mediaHost.init(MediaHierarchyManager.LOCATION_LOCKSCREEN) } + /** + * Is the media player visible? + */ + var visible = false + private set + var visibilityChangedListener: ((Boolean) -> Unit)? = null /** @@ -106,11 +112,11 @@ class KeyguardMediaController @Inject constructor( val keyguardOrUserSwitcher = (statusBarStateController.state == StatusBarState.KEYGUARD || statusBarStateController.state == StatusBarState.FULLSCREEN_USER_SWITCHER) // mediaHost.visible required for proper animations handling - val shouldBeVisible = mediaHost.visible && + visible = mediaHost.visible && !bypassController.bypassEnabled && keyguardOrUserSwitcher && notifLockscreenUserManager.shouldShowLockscreenNotifications() - if (shouldBeVisible) { + if (visible) { showMediaPlayer() } else { hideMediaPlayer() diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index 60e832ace7b0..73dfe5e68d9a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt @@ -26,6 +26,7 @@ import android.util.MathUtils import android.view.View import android.view.ViewGroup import android.view.ViewGroupOverlay +import com.android.systemui.R import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.WakefulnessLifecycle @@ -35,6 +36,7 @@ import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.stack.StackStateAnimator import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.animation.UniqueObjectHostView import javax.inject.Inject @@ -74,6 +76,7 @@ class MediaHierarchyManager @Inject constructor( private val bypassController: KeyguardBypassController, private val mediaCarouselController: MediaCarouselController, private val notifLockscreenUserManager: NotificationLockscreenUserManager, + configurationController: ConfigurationController, wakefulnessLifecycle: WakefulnessLifecycle, private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager ) { @@ -183,6 +186,58 @@ class MediaHierarchyManager @Inject constructor( } /** + * distance that the full shade transition takes in order for media to fully transition to the + * shade + */ + private var distanceForFullShadeTransition = 0 + + /** + * Delay after which the media will start transitioning to the full shade on the lockscreen. + */ + private var fullShadeTransitionDelay = 0 + + /** + * The amount of progress we are currently in if we're transitioning to the full shade. + * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full + * shade. + */ + private var fullShadeTransitionProgress = 0f + set(value) { + if (field == value) { + return + } + field = value + if (bypassController.bypassEnabled) { + return + } + updateDesiredLocation() + if (value >= 0) { + updateTargetState() + applyTargetStateIfNotAnimating() + } + } + + private val isTransitioningToFullShade: Boolean + get() = fullShadeTransitionProgress != 0f && !bypassController.bypassEnabled + + /** + * Set the amount of pixels we have currently dragged down if we're transitioning to the full + * shade. 0.0f means we're not transitioning yet. + */ + fun setTransitionToFullShadeAmount(value: Float) { + // If we're transitioning starting on the shade_locked, we don't want any delay and rather + // have it aligned with the rest of the animation + val delay = if (statusbarState == StatusBarState.KEYGUARD) { + fullShadeTransitionDelay + } else { + 0 + } + val progress = MathUtils.saturate((value - delay) / + (distanceForFullShadeTransition - delay)) + fullShadeTransitionProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(progress) + } + + /** * Is the shade currently collapsing from the expanded qs? If we're on the lockscreen and in qs, * we wouldn't want to transition in that case. */ @@ -242,6 +297,12 @@ class MediaHierarchyManager @Inject constructor( } init { + updateConfiguration() + configurationController.addCallback(object : ConfigurationController.ConfigurationListener { + override fun onDensityOrFontScaleChanged() { + updateConfiguration() + } + }) statusBarStateController.addCallback(object : StatusBarStateController.StateListener { override fun onStatePreChange(oldState: Int, newState: Int) { // We're updating the location before the state change happens, since we want the @@ -312,6 +373,13 @@ class MediaHierarchyManager @Inject constructor( }) } + private fun updateConfiguration() { + distanceForFullShadeTransition = context.resources.getDimensionPixelSize( + R.dimen.lockscreen_shade_qs_transition_distance) + fullShadeTransitionDelay = context.resources.getDimensionPixelSize( + R.dimen.lockscreen_shade_media_transition_start_delay) + } + /** * Register a media host and create a view can be attached to a view hierarchy * and where the players will be placed in when the host is the currently desired state. @@ -546,6 +614,9 @@ class MediaHierarchyManager @Inject constructor( if (progress >= 0) { return progress } + if (isTransitioningToFullShade) { + return fullShadeTransitionProgress + } return -1.0f } @@ -643,6 +714,7 @@ class MediaHierarchyManager @Inject constructor( val location = when { qsExpansion > 0.0f && !onLockscreen -> LOCATION_QS qsExpansion > 0.4f && onLockscreen -> LOCATION_QS + onLockscreen && isTransitioningToFullShade -> LOCATION_QQS onLockscreen && allowedOnLockscreen -> LOCATION_LOCKSCREEN else -> LOCATION_QQS } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 53b4d5f7d5f8..34c654c9135d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -112,6 +112,17 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca * otherwise. */ private boolean mTranslateWhileExpanding; + private boolean mPulseExpanding; + + /** + * Are we currently transitioning from lockscreen to the full shade? + */ + private boolean mTransitioningToFullShade; + + /** + * Whether the next Quick settings + */ + private boolean mAnimateNextQsUpdate; @Inject public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, @@ -265,6 +276,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } } + @Override + public boolean isFullyCollapsed() { + return mLastQSExpansion == 0.0f || mLastQSExpansion == -1; + } + private void setEditLocation(View view) { View edit = view.findViewById(android.R.id.edit); int[] loc = edit.getLocationOnScreen(); @@ -335,14 +351,22 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } @Override - public void setShowCollapsedOnKeyguard(boolean showCollapsedOnKeyguard) { - if (showCollapsedOnKeyguard != mShowCollapsedOnKeyguard) { - mShowCollapsedOnKeyguard = showCollapsedOnKeyguard; + public void setPulseExpanding(boolean pulseExpanding) { + if (pulseExpanding != mPulseExpanding) { + mPulseExpanding = pulseExpanding; + updateShowCollapsedOnKeyguard(); + } + } + + private void updateShowCollapsedOnKeyguard() { + boolean showCollapsed = mPulseExpanding || mTransitioningToFullShade; + if (showCollapsed != mShowCollapsedOnKeyguard) { + mShowCollapsedOnKeyguard = showCollapsed; updateQsState(); if (mQSAnimator != null) { - mQSAnimator.setShowCollapsedOnKeyguard(showCollapsedOnKeyguard); + mQSAnimator.setShowCollapsedOnKeyguard(showCollapsed); } - if (!showCollapsedOnKeyguard && isKeyguardShowing()) { + if (!showCollapsed && isKeyguardShowing()) { setQsExpansion(mLastQSExpansion, 0); } } @@ -411,13 +435,24 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } @Override - public void setQsExpansion(float expansion, float headerTranslation) { - if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + headerTranslation); + public void setTransitionToFullShadeAmount(float pxAmount, boolean animated) { + boolean isTransitioningToFullShade = pxAmount > 0; + if (isTransitioningToFullShade != mTransitioningToFullShade) { + mTransitioningToFullShade = isTransitioningToFullShade; + updateShowCollapsedOnKeyguard(); + setQsExpansion(mLastQSExpansion, mLastHeaderTranslation); + } + } + @Override + public void setQsExpansion(float expansion, float proposedTranslation) { + if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + proposedTranslation); + float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation; if (mQSAnimator != null) { final boolean showQSOnLockscreen = expansion > 0; final boolean showQSUnlocked = headerTranslation == 0 || !mTranslateWhileExpanding; - mQSAnimator.startAlphaAnimation(showQSOnLockscreen || showQSUnlocked); + mQSAnimator.startAlphaAnimation(showQSOnLockscreen || showQSUnlocked + || mTransitioningToFullShade); } mContainer.setExpansion(expansion); final float translationScaleY = (mTranslateWhileExpanding @@ -542,18 +577,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } @Override - public void animateHeaderSlidingIn(long delay) { - if (DEBUG) Log.d(TAG, "animateHeaderSlidingIn"); - // If the QS is already expanded we don't need to slide in the header as it's already - // visible. - if (!mQsExpanded && getView().getTranslationY() != 0) { - mHeaderAnimating = true; - mDelay = delay; - getView().getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn); - } - } - - @Override public void animateHeaderSlidingOut() { if (DEBUG) Log.d(TAG, "animateHeaderSlidingOut"); if (getView().getY() == -mHeader.getHeight()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index 6d91ccba0156..12f569ce9fbc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -5,35 +5,489 @@ import android.animation.AnimatorListenerAdapter import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.content.Context +import android.content.res.Configuration +import android.os.SystemClock +import android.util.DisplayMetrics +import android.util.MathUtils import android.view.MotionEvent import android.view.View import android.view.ViewConfiguration +import androidx.annotation.VisibleForTesting +import com.android.internal.logging.nano.MetricsProto.MetricsEvent import com.android.systemui.ExpandHelper import com.android.systemui.Gefingerpoken -import com.android.systemui.animation.Interpolators import com.android.systemui.R +import com.android.systemui.animation.Interpolators import com.android.systemui.classifier.Classifier import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.media.MediaHierarchyManager +import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.qs.QS +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView +import com.android.systemui.statusbar.notification.stack.AmbientState +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.statusbar.phone.LockscreenGestureLogger +import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent +import com.android.systemui.statusbar.phone.NotificationPanelViewController +import com.android.systemui.statusbar.phone.ScrimController +import com.android.systemui.statusbar.phone.StatusBar +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.util.Utils +import javax.inject.Inject + +private const val SPRING_BACK_ANIMATION_LENGTH_MS = 375L +private const val RUBBERBAND_FACTOR_STATIC = 0.15f +private const val RUBBERBAND_FACTOR_EXPANDABLE = 0.5f + +/** + * A class that controls the lockscreen to shade transition + */ +@SysUISingleton +class LockscreenShadeTransitionController @Inject constructor( + private val statusBarStateController: SysuiStatusBarStateController, + private val lockscreenGestureLogger: LockscreenGestureLogger, + private val keyguardBypassController: KeyguardBypassController, + private val lockScreenUserManager: NotificationLockscreenUserManager, + private val falsingCollector: FalsingCollector, + private val ambientState: AmbientState, + private val displayMetrics: DisplayMetrics, + private val mediaHierarchyManager: MediaHierarchyManager, + private val scrimController: ScrimController, + private val featureFlags: FeatureFlags, + private val context: Context, + configurationController: ConfigurationController, + falsingManager: FalsingManager +) { + private var useSplitShade: Boolean = false + private lateinit var nsslController: NotificationStackScrollLayoutController + lateinit var notificationPanelController: NotificationPanelViewController + lateinit var statusbar: StatusBar + lateinit var qS: QS + + /** + * A handler that handles the next keyguard dismiss animation. + */ + private var animationHandlerOnKeyguardDismiss: ((Long) -> Unit)? = null + + /** + * The entry that was just dragged down on. + */ + private var draggedDownEntry: NotificationEntry? = null + + /** + * The current animator if any + */ + @VisibleForTesting + internal var dragDownAnimator: ValueAnimator? = null + + /** + * Distance that the full shade transition takes in order for scrim to fully transition to + * the shade (in alpha) + */ + private var scrimTransitionDistance = 0 + + /** + * Distance that the full transition takes in order for us to fully transition to the shade + */ + private var fullTransitionDistance = 0 + + /** + * Flag to make sure that the dragDownAmount is applied to the listeners even when in the + * locked down shade. + */ + private var forceApplyAmount = false + + /** + * A flag to suppress the default animation when unlocking in the locked down shade. + */ + private var nextHideKeyguardNeedsNoAnimation = false + + /** + * The touch helper responsible for the drag down animation. + */ + val touchHelper = DragDownHelper(falsingManager, falsingCollector, this, context) + + init { + updateResources() + configurationController.addCallback(object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + updateResources() + touchHelper.updateResources(context) + } + }) + } + + private fun updateResources() { + scrimTransitionDistance = context.resources.getDimensionPixelSize( + R.dimen.lockscreen_shade_scrim_transition_distance) + fullTransitionDistance = context.resources.getDimensionPixelSize( + R.dimen.lockscreen_shade_qs_transition_distance) + useSplitShade = Utils.shouldUseSplitNotificationShade(featureFlags, context.resources) + } + + fun setStackScroller(nsslController: NotificationStackScrollLayoutController) { + this.nsslController = nsslController + touchHelper.host = nsslController.view + touchHelper.expandCallback = nsslController.expandHelperCallback + } + + /** + * Initialize the shelf controller such that clicks on it will expand the shade + */ + fun bindController(notificationShelfController: NotificationShelfController) { + // Bind the click listener of the shelf to go to the full shade + notificationShelfController.setOnClickListener { + if (statusBarStateController.state == StatusBarState.KEYGUARD) { + statusbar.wakeUpIfDozing(SystemClock.uptimeMillis(), it, "SHADE_CLICK") + goToLockedShade(it) + } + } + } + + /** + * @return true if the interaction is accepted, false if it should be cancelled + */ + internal fun canDragDown(): Boolean { + return (statusBarStateController.state == StatusBarState.KEYGUARD || + nsslController.isInLockedDownShade()) && + qS.isFullyCollapsed + } + + /** + * Called by the touch helper when when a gesture has completed all the way and released. + */ + internal fun onDraggedDown(startingChild: View?, dragLengthY: Int) { + if (canDragDown()) { + if (nsslController.isInLockedDownShade()) { + statusBarStateController.setLeaveOpenOnKeyguardHide(true) + statusbar.dismissKeyguardThenExecute(OnDismissAction { + nextHideKeyguardNeedsNoAnimation = true + false + }, + null /* cancelRunnable */, false /* afterKeyguardGone */) + } else { + lockscreenGestureLogger.write( + MetricsEvent.ACTION_LS_SHADE, + (dragLengthY / displayMetrics.density).toInt(), + 0 /* velocityDp */) + lockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_PULL_SHADE_OPEN) + if (!ambientState.isDozing() || startingChild != null) { + // go to locked shade while animating the drag down amount from its current + // value + val animationHandler = { delay: Long -> + if (startingChild is ExpandableNotificationRow) { + startingChild.onExpandedByGesture( + true /* drag down is always an open */) + } + notificationPanelController.animateToFullShade(delay) + notificationPanelController.setTransitionToFullShadeAmount(0f, + true /* animated */, delay) + + // Let's reset ourselves, ready for the next animation + + // changing to shade locked will make isInLockDownShade true, so let's + // override that + forceApplyAmount = true + // Reset the behavior. At this point the animation is already started + dragDownAmount = 0f + forceApplyAmount = false + } + val cancelRunnable = Runnable { setDragDownAmountAnimated(0f) } + goToLockedShadeInternal(startingChild, animationHandler, cancelRunnable) + } + } + } else { + setDragDownAmountAnimated(0f) + } + } + + /** + * Called by the touch helper when the drag down was aborted and should be reset. + */ + internal fun onDragDownReset() { + nsslController.setDimmed(true /* dimmed */, true /* animated */) + nsslController.resetScrollPosition() + nsslController.resetCheckSnoozeLeavebehind() + setDragDownAmountAnimated(0f) + } + + /** + * The user has dragged either above or below the threshold which changes the dimmed state. + * @param above whether they dragged above it + */ + internal fun onCrossedThreshold(above: Boolean) { + nsslController.setDimmed(!above /* dimmed */, true /* animate */) + } + + /** + * Called by the touch helper when the drag down was started + */ + internal fun onDragDownStarted() { + nsslController.cancelLongPress() + nsslController.checkSnoozeLeavebehind() + dragDownAnimator?.cancel() + } + + /** + * Do we need a falsing check currently? + */ + internal val isFalsingCheckNeeded: Boolean + get() = statusBarStateController.state == StatusBarState.KEYGUARD + + /** + * Is dragging down enabled on a given view + * @param view The view to check or `null` to check if it's enabled at all + */ + internal fun isDragDownEnabledForView(view: ExpandableView?): Boolean { + if (isDragDownAnywhereEnabled) { + return true + } + if (nsslController.isInLockedDownShade()) { + if (view == null) { + // Dragging down is allowed in general + return true + } + if (view is ExpandableNotificationRow) { + // Only drag down on sensitive views, otherwise the ExpandHelper will take this + return view.entry.isSensitive + } + } + return false + } + + /** + * @return if drag down is enabled anywhere, not just on selected views. + */ + internal val isDragDownAnywhereEnabled: Boolean + get() = (statusBarStateController.getState() == StatusBarState.KEYGUARD && + !keyguardBypassController.bypassEnabled && + qS.isFullyCollapsed) + + /** + * The amount in pixels that the user has dragged down. + */ + internal var dragDownAmount = 0f + set(value) { + if (field != value || forceApplyAmount) { + field = value + if (!nsslController.isInLockedDownShade() || forceApplyAmount) { + nsslController.setTransitionToFullShadeAmount(field) + notificationPanelController.setTransitionToFullShadeAmount(field, + false /* animate */, 0 /* delay */) + mediaHierarchyManager.setTransitionToFullShadeAmount(field) + val scrimProgress = MathUtils.saturate(field / scrimTransitionDistance) + scrimController.setTransitionToFullShadeProgress(scrimProgress) + // TODO: appear qs also in split shade + val qsAmount = if (useSplitShade) 0f else field + qS.setTransitionToFullShadeAmount(qsAmount, false /* animate */) + } + } + } + + private fun setDragDownAmountAnimated( + target: Float, + delay: Long = 0, + endlistener: (() -> Unit)? = null + ) { + val dragDownAnimator = ValueAnimator.ofFloat(dragDownAmount, target) + dragDownAnimator.interpolator = Interpolators.FAST_OUT_SLOW_IN + dragDownAnimator.duration = SPRING_BACK_ANIMATION_LENGTH_MS + dragDownAnimator.addUpdateListener { animation: ValueAnimator -> + dragDownAmount = animation.animatedValue as Float + } + if (delay > 0) { + dragDownAnimator.startDelay = delay + } + if (endlistener != null) { + dragDownAnimator.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + endlistener.invoke() + } + }) + } + dragDownAnimator.start() + this.dragDownAnimator = dragDownAnimator + } + + /** + * Animate appear the drag down amount. + */ + private fun animateAppear(delay: Long = 0) { + // changing to shade locked will make isInLockDownShade true, so let's override + // that + forceApplyAmount = true + + // we set the value initially to 1 pixel, since that will make sure we're + // transitioning to the full shade. this is important to avoid flickering, + // as the below animation only starts once the shade is unlocked, which can + // be a couple of frames later. if we're setting it to 0, it will use the + // default inset and therefore flicker + dragDownAmount = 1f + setDragDownAmountAnimated(fullTransitionDistance.toFloat(), delay = delay) { + // End listener: + // Reset + dragDownAmount = 0f + forceApplyAmount = false + } + } + + /** + * Ask this controller to go to the locked shade, changing the state change and doing + * an animation, where the qs appears from 0 from the top + * + * If secure with redaction: Show bouncer, go to unlocked shade. + * If secure without redaction or no security: Go to [StatusBarState.SHADE_LOCKED]. + * + * @param expandView The view to expand after going to the shade + * @param needsQSAnimation if this needs the quick settings to slide in from the top or if + * that's already handled separately + */ + @JvmOverloads + fun goToLockedShade(expandedView: View?, needsQSAnimation: Boolean = true) { + if (statusBarStateController.state == StatusBarState.KEYGUARD) { + val animationHandler: ((Long) -> Unit)? + if (needsQSAnimation) { + // Let's use the default animation + animationHandler = null + } else { + // Let's only animate notifications + animationHandler = { delay: Long -> + notificationPanelController.animateToFullShade(delay) + } + } + goToLockedShadeInternal(expandedView, animationHandler, + cancelAction = null) + } + } + + /** + * If secure with redaction: Show bouncer, go to unlocked shade. + * + * If secure without redaction or no security: Go to [StatusBarState.SHADE_LOCKED]. + * + * @param expandView The view to expand after going to the shade. + * @param animationHandler The handler which performs the go to full shade animation. If null, + * the default handler will do the animation, otherwise the caller is + * responsible for the animation. The input value is a Long for the + * delay for the animation. + * @param cancelAction The runnable to invoke when the transition is aborted. This happens if + * the user goes to the bouncer and goes back. + */ + private fun goToLockedShadeInternal( + expandView: View?, + animationHandler: ((Long) -> Unit)? = null, + cancelAction: Runnable? = null + ) { + if (statusbar.isShadeDisabled) { + cancelAction?.run() + return + } + var userId: Int = lockScreenUserManager.getCurrentUserId() + var entry: NotificationEntry? = null + if (expandView is ExpandableNotificationRow) { + entry = expandView.entry + entry.setUserExpanded(true /* userExpanded */, true /* allowChildExpansion */) + // Indicate that the group expansion is changing at this time -- this way the group + // and children backgrounds / divider animations will look correct. + entry.setGroupExpansionChanging(true) + userId = entry.sbn.userId + } + var fullShadeNeedsBouncer = (!lockScreenUserManager.userAllowsPrivateNotificationsInPublic( + lockScreenUserManager.getCurrentUserId()) || + !lockScreenUserManager.shouldShowLockscreenNotifications() || + falsingCollector.shouldEnforceBouncer()) + if (keyguardBypassController.bypassEnabled) { + fullShadeNeedsBouncer = false + } + if (lockScreenUserManager.isLockscreenPublicMode(userId) && fullShadeNeedsBouncer) { + statusBarStateController.setLeaveOpenOnKeyguardHide(true) + var onDismissAction: OnDismissAction? = null + if (animationHandler != null) { + onDismissAction = OnDismissAction { + // We're waiting on keyguard to hide before triggering the action, + // as that will make the animation work properly + animationHandlerOnKeyguardDismiss = animationHandler + false + } + } + val cancelHandler = Runnable { + draggedDownEntry?.apply { + setUserLocked(false) + notifyHeightChanged(false /* needsAnimation */) + draggedDownEntry = null + } + cancelAction?.run() + } + statusbar.showBouncerWithDimissAndCancelIfKeyguard(onDismissAction, cancelHandler) + draggedDownEntry = entry + } else { + statusBarStateController.setState(StatusBarState.SHADE_LOCKED) + // This call needs to be after updating the shade state since otherwise + // the scrimstate resets too early + if (animationHandler != null) { + animationHandler.invoke(0 /* delay */) + } else { + performDefaultGoToFullShadeAnimation(0) + } + } + } + + /** + * Notify this handler that the keyguard was just dismissed and that a animation to + * the full shade should happen. + */ + fun onHideKeyguard(delay: Long) { + if (animationHandlerOnKeyguardDismiss != null) { + animationHandlerOnKeyguardDismiss!!.invoke(delay) + animationHandlerOnKeyguardDismiss = null + } else { + if (nextHideKeyguardNeedsNoAnimation) { + nextHideKeyguardNeedsNoAnimation = false + } else { + performDefaultGoToFullShadeAnimation(delay) + } + } + draggedDownEntry?.apply { + setUserLocked(false) + draggedDownEntry = null + } + } + + /** + * Perform the default appear animation when going to the full shade. This is called when + * not triggered by gestures, e.g. when clicking on the shelf or expand button. + */ + private fun performDefaultGoToFullShadeAnimation(delay: Long) { + notificationPanelController.animateToFullShade(delay) + animateAppear(delay) + } +} /** * A utility class to enable the downward swipe on the lockscreen to go to the full shade and expand * the notification where the drag started. */ -class DragDownHelper(private val falsingManager: FalsingManager, - private val expandCallback: ExpandHelper.Callback, - private val dragDownCallback: DragDownCallback, - private val falsingCollector: FalsingCollector, - private val host: View, - context: Context): Gefingerpoken { - - private val minDragDistance: Int +class DragDownHelper( + private val falsingManager: FalsingManager, + private val falsingCollector: FalsingCollector, + private val dragDownCallback: LockscreenShadeTransitionController, + context: Context +) : Gefingerpoken { + + private var dragDownAmountOnStart = 0.0f + lateinit var expandCallback: ExpandHelper.Callback + lateinit var host: View + + private var minDragDistance = 0 private var initialTouchX = 0f private var initialTouchY = 0f - private val touchSlop: Float - private val slopMultiplier: Float + private var touchSlop = 0f + private var slopMultiplier = 0f private val temp2 = IntArray(2) private var draggedFarEnough = false private var startingChild: ExpandableView? = null @@ -41,7 +495,23 @@ class DragDownHelper(private val falsingManager: FalsingManager, var isDraggingDown = false private set + private val isFalseTouch: Boolean + get() { + return if (!dragDownCallback.isFalsingCheckNeeded) { + false + } else { + falsingManager.isFalseTouch(Classifier.NOTIFICATION_DRAG_DOWN) || !draggedFarEnough + } + } + + val isDragDownEnabled: Boolean + get() = dragDownCallback.isDragDownEnabledForView(null) + init { + updateResources(context) + } + + fun updateResources(context: Context) { minDragDistance = context.resources.getDimensionPixelSize( R.dimen.keyguard_drag_down_min_distance) val configuration = ViewConfiguration.get(context) @@ -74,7 +544,8 @@ class DragDownHelper(private val falsingManager: FalsingManager, captureStartingChild(initialTouchX, initialTouchY) initialTouchY = y initialTouchX = x - dragDownCallback.onTouchSlopExceeded() + dragDownCallback.onDragDownStarted() + dragDownAmountOnStart = dragDownCallback.dragDownAmount return startingChild != null || dragDownCallback.isDragDownAnywhereEnabled } } @@ -92,10 +563,9 @@ class DragDownHelper(private val falsingManager: FalsingManager, MotionEvent.ACTION_MOVE -> { lastHeight = y - initialTouchY captureStartingChild(initialTouchX, initialTouchY) + dragDownCallback.dragDownAmount = lastHeight + dragDownAmountOnStart if (startingChild != null) { handleExpansion(lastHeight, startingChild!!) - } else { - dragDownCallback.setEmptyDragAmount(lastHeight) } if (lastHeight > minDragDistance) { if (!draggedFarEnough) { @@ -110,12 +580,10 @@ class DragDownHelper(private val falsingManager: FalsingManager, } return true } - MotionEvent.ACTION_UP -> if (!falsingManager.isUnlockingDisabled && !isFalseTouch - && dragDownCallback.canDragDown()) { + MotionEvent.ACTION_UP -> if (!falsingManager.isUnlockingDisabled && !isFalseTouch && + dragDownCallback.canDragDown()) { dragDownCallback.onDraggedDown(startingChild, (y - initialTouchY).toInt()) - if (startingChild == null) { - cancelExpansion() - } else { + if (startingChild != null) { expandCallback.setUserLockedChild(startingChild, false) startingChild = null } @@ -132,15 +600,6 @@ class DragDownHelper(private val falsingManager: FalsingManager, return false } - private val isFalseTouch: Boolean - get() { - return if (!dragDownCallback.isFalsingCheckNeeded) { - false - } else { - falsingManager.isFalseTouch(Classifier.NOTIFICATION_DRAG_DOWN) || !draggedFarEnough - } - } - private fun captureStartingChild(x: Float, y: Float) { if (startingChild == null) { startingChild = findView(x, y) @@ -174,7 +633,7 @@ class DragDownHelper(private val falsingManager: FalsingManager, child.actualHeight = (child.collapsedHeight + rubberband).toInt() } - private fun cancelExpansion(child: ExpandableView) { + private fun cancelChildExpansion(child: ExpandableView) { if (child.actualHeight == child.collapsedHeight) { expandCallback.setUserLockedChild(child, false) return @@ -182,7 +641,7 @@ class DragDownHelper(private val falsingManager: FalsingManager, val anim = ObjectAnimator.ofInt(child, "actualHeight", child.actualHeight, child.collapsedHeight) anim.interpolator = Interpolators.FAST_OUT_SLOW_IN - anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS.toLong() + anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS anim.addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { expandCallback.setUserLockedChild(child, false) @@ -191,73 +650,18 @@ class DragDownHelper(private val falsingManager: FalsingManager, anim.start() } - private fun cancelExpansion() { - val anim = ValueAnimator.ofFloat(lastHeight, 0f) - anim.interpolator = Interpolators.FAST_OUT_SLOW_IN - anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS.toLong() - anim.addUpdateListener { animation: ValueAnimator -> - dragDownCallback.setEmptyDragAmount(animation.animatedValue as Float) - } - anim.start() - } - private fun stopDragging() { falsingCollector.onNotificationStopDraggingDown() if (startingChild != null) { - cancelExpansion(startingChild!!) + cancelChildExpansion(startingChild!!) startingChild = null - } else { - cancelExpansion() } isDraggingDown = false dragDownCallback.onDragDownReset() } - private fun findView(x: Float, y: Float): ExpandableView { + private fun findView(x: Float, y: Float): ExpandableView? { host.getLocationOnScreen(temp2) - return expandCallback.getChildAtRawPosition(x + temp2[0] , y + temp2[1]) - } - - val isDragDownEnabled: Boolean - get() = dragDownCallback.isDragDownEnabledForView(null) - - interface DragDownCallback { - - /** - * @return true if the interaction is accepted, false if it should be cancelled - */ - fun canDragDown(): Boolean - - /** - * Call when a view has been dragged. - **/ - fun onDraggedDown(startingChild: View?, dragLengthY: Int) - fun onDragDownReset() - - /** - * The user has dragged either above or below the threshold - * @param above whether they dragged above it - */ - fun onCrossedThreshold(above: Boolean) - fun onTouchSlopExceeded() - fun setEmptyDragAmount(amount: Float) - val isFalsingCheckNeeded: Boolean - - /** - * Is dragging down enabled on a given view - * @param view The view to check or `null` to check if it's enabled at all - */ - fun isDragDownEnabledForView(view: ExpandableView?): Boolean - - /** - * @return if drag down is enabled anywhere, not just on selected views. - */ - val isDragDownAnywhereEnabled: Boolean - } - - companion object { - private const val RUBBERBAND_FACTOR_EXPANDABLE = 0.5f - private const val RUBBERBAND_FACTOR_STATIC = 0.15f - private const val SPRING_BACK_ANIMATION_LENGTH_MS = 375 + return expandCallback.getChildAtRawPosition(x + temp2[0], y + temp2[1]) } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt index 84465a88eed0..9765ace7179f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt @@ -42,7 +42,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationRoundnessMa import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.phone.HeadsUpManagerPhone import com.android.systemui.statusbar.phone.KeyguardBypassController -import com.android.systemui.statusbar.phone.ShadeController import javax.inject.Inject import kotlin.math.max @@ -59,6 +58,7 @@ constructor( private val roundnessManager: NotificationRoundnessManager, private val statusBarStateController: StatusBarStateController, private val falsingManager: FalsingManager, + private val lockscreenShadeTransitionController: LockscreenShadeTransitionController, private val falsingCollector: FalsingCollector ) : Gefingerpoken { companion object { @@ -66,7 +66,6 @@ constructor( private val SPRING_BACK_ANIMATION_LENGTH_MS = 375 } private val mPowerManager: PowerManager? - private lateinit var shadeController: ShadeController private val mMinDragDistance: Int private var mInitialTouchX: Float = 0.0f @@ -95,7 +94,7 @@ constructor( var leavingLockscreen: Boolean = false private set private val mTouchSlop: Float - private lateinit var expansionCallback: ExpansionCallback + private lateinit var overStretchHandler: OverStretchHandler private lateinit var stackScrollerController: NotificationStackScrollLayoutController private val mTemp2 = IntArray(2) private var mDraggedFarEnough: Boolean = false @@ -103,7 +102,7 @@ constructor( private var mPulsing: Boolean = false var isWakingToShadeLocked: Boolean = false private set - private var mEmptyDragAmount: Float = 0.0f + private var overStretchAmount: Float = 0.0f private var mWakeUpHeight: Float = 0.0f private var mReachedWakeUpHeight: Boolean = false private var velocityTracker: VelocityTracker? = null @@ -215,6 +214,7 @@ constructor( private fun finishExpansion() { resetClock() + val startingChild = mStartingChild if (mStartingChild != null) { setUserLocked(mStartingChild!!, false) mStartingChild = null @@ -225,7 +225,8 @@ constructor( mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), WAKE_REASON_GESTURE, "com.android.systemui:PULSEDRAG") } - shadeController.goToLockedShade(mStartingChild) + lockscreenShadeTransitionController.goToLockedShade(startingChild, + needsQSAnimation = false) leavingLockscreen = true isExpanding = false if (mStartingChild is ExpandableNotificationRow) { @@ -252,8 +253,8 @@ constructor( true /* increaseSpeed */) expansionHeight = max(mWakeUpHeight, expansionHeight) } - val emptyDragAmount = wakeUpCoordinator.setPulseHeight(expansionHeight) - setEmptyDragAmount(emptyDragAmount * RUBBERBAND_FACTOR_STATIC) + val dragDownAmount = wakeUpCoordinator.setPulseHeight(expansionHeight) + setOverStretchAmount(dragDownAmount) } private fun captureStartingChild(x: Float, y: Float) { @@ -265,9 +266,9 @@ constructor( } } - private fun setEmptyDragAmount(amount: Float) { - mEmptyDragAmount = amount - expansionCallback.setEmptyDragAmount(amount) + private fun setOverStretchAmount(amount: Float) { + overStretchAmount = amount + overStretchHandler.setOverStretchAmount(amount) } private fun reset(child: ExpandableView) { @@ -294,10 +295,12 @@ constructor( } private fun resetClock() { - val anim = ValueAnimator.ofFloat(mEmptyDragAmount, 0f) + val anim = ValueAnimator.ofFloat(overStretchAmount, 0f) anim.interpolator = Interpolators.FAST_OUT_SLOW_IN anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS.toLong() - anim.addUpdateListener { animation -> setEmptyDragAmount(animation.animatedValue as Float) } + anim.addUpdateListener { + animation -> setOverStretchAmount(animation.animatedValue as Float) + } anim.start() } @@ -329,11 +332,9 @@ constructor( fun setUp( stackScrollerController: NotificationStackScrollLayoutController, - expansionCallback: ExpansionCallback, - shadeController: ShadeController + overStrechHandler: OverStretchHandler ) { - this.expansionCallback = expansionCallback - this.shadeController = shadeController + this.overStretchHandler = overStrechHandler this.stackScrollerController = stackScrollerController } @@ -345,7 +346,11 @@ constructor( isWakingToShadeLocked = false } - interface ExpansionCallback { - fun setEmptyDragAmount(amount: Float) + interface OverStretchHandler { + + /** + * Set the overstretch amount in pixels This will be rubberbanded later + */ + fun setOverStretchAmount(amount: Float) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index caf47207de07..66d23477436f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -95,6 +95,7 @@ public class AmbientState { /** Height of the notifications panel without top padding when expansion completes. */ private float mStackEndHeight; + private float mTransitionToFullShadeAmount; /** * @return Height of the notifications panel without top padding when expansion completes. @@ -595,6 +596,21 @@ public class AmbientState { } /** + * Set the amount of pixels we have currently dragged down if we're transitioning to the full + * shade. 0.0f means we're not transitioning yet. + */ + public void setTransitionToFullShadeAmount(float transitionToFullShadeAmount) { + mTransitionToFullShadeAmount = transitionToFullShadeAmount; + } + + /** + * get + */ + public float getTransitionToFullShadeAmount() { + return mTransitionToFullShadeAmount; + } + + /** * Returns the currently tracked heads up row, if there is one and it is currently above the * shelf (still appearing). */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 4dbdb133e1a7..3244ff915d87 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -46,7 +46,6 @@ import android.os.Bundle; import android.os.UserHandle; import android.provider.Settings; import android.util.AttributeSet; -import android.util.DisplayMetrics; import android.util.Log; import android.util.MathUtils; import android.util.Pair; @@ -71,17 +70,14 @@ import android.widget.ScrollView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.internal.jank.InteractionJankMonitor; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.keyguard.KeyguardSliceView; import com.android.settingslib.Utils; -import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.ExpandHelper; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.DragDownHelper.DragDownCallback; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -89,7 +85,6 @@ import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.ExpandAnimationParameters; import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.NotificationActivityStarter; @@ -108,9 +103,6 @@ import com.android.systemui.statusbar.notification.row.ForegroundServiceDungeonV import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; -import com.android.systemui.statusbar.phone.LockscreenGestureLogger; -import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; -import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.HeadsUpUtil; @@ -151,7 +143,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable */ private static final int DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX = 1; private KeyguardBypassEnabledProvider mKeyguardBypassEnabledProvider; - private final SysuiStatusBarStateController mStatusbarStateController; private ExpandHelper mExpandHelper; private NotificationSwipeHelper mSwipeHelper; @@ -433,13 +424,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private ShadeController mShadeController; private Runnable mOnStackYChanged; - private final DisplayMetrics mDisplayMetrics = Dependency.get(DisplayMetrics.class); - private final LockscreenGestureLogger mLockscreenGestureLogger = - Dependency.get(LockscreenGestureLogger.class); protected boolean mClearAllEnabled; private Interpolator mHideXInterpolator = Interpolators.FAST_OUT_SLOW_IN; - private NotificationPanelViewController mNotificationPanelController; private final NotificationSectionsManager mSectionsManager; private ForegroundServiceDungeonView mFgsSectionView; @@ -449,6 +436,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private boolean mWillExpand; private int mGapHeight; + /** + * The extra inset during the full shade transition + */ + private float mExtraTopInsetForFullShadeTransition; + private int mWaterfallTopInset; private NotificationStackScrollLayoutController mController; @@ -496,7 +488,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable NotificationSectionsManager notificationSectionsManager, GroupMembershipManager groupMembershipManager, GroupExpansionManager groupExpansionManager, - SysuiStatusBarStateController statusbarStateController, AmbientState ambientState, FeatureFlags featureFlags) { super(context, attrs, 0, 0); @@ -535,7 +526,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll); mGroupMembershipManager = groupMembershipManager; mGroupExpansionManager = groupExpansionManager; - mStatusbarStateController = statusbarStateController; } void initializeForegroundServiceSection(ForegroundServiceDungeonView fgsSectionView) { @@ -1142,8 +1132,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable */ private void updateStackPosition() { // Consider interpolating from an mExpansionStartY for use on lockscreen and AOD + float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition; final float fraction = mAmbientState.getExpansionFraction(); - final float stackY = MathUtils.lerp(0, mTopPadding, fraction); + final float stackY = MathUtils.lerp(0, endTopPosition, fraction); mAmbientState.setStackY(stackY); if (mOnStackYChanged != null) { mOnStackYChanged.run(); @@ -4278,6 +4269,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable + mGapHeight; } + /** + * @return the padding after the media header on the lockscreen + */ + public int getPaddingAfterMedia() { + return mGapHeight + mPaddingBetweenElements; + } + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public int getEmptyShadeViewHeight() { return mEmptyShadeView.getHeight(); @@ -4933,12 +4931,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable getChildCount() - offsetFromEnd); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public void setNotificationPanelController( - NotificationPanelViewController notificationPanelViewController) { - mNotificationPanelController = notificationPanelViewController; - } - /** * Set how far the wake up is when waking up from pulsing. This is a height and will adjust the * notification positions accordingly. @@ -5155,6 +5147,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } /** + * Sets the extra top inset for the full shade transition. This is needed to compensate for + * media transitioning to quick settings + */ + public void setExtraTopInsetForFullShadeTransition(float inset) { + mExtraTopInsetForFullShadeTransition = inset; + updateStackPosition(); + requestChildrenUpdate(); + } + + /** * A listener that is notified when the empty space below the notifications is clicked on */ @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) @@ -5526,108 +5528,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - public void setKeyguardMediaControllorVisible(boolean keyguardMediaControllorVisible) { - mKeyguardMediaControllorVisible = keyguardMediaControllorVisible; - } - void resetCheckSnoozeLeavebehind() { setCheckForLeaveBehind(true); } - // ---------------------- DragDownHelper.OnDragDownListener ------------------------------------ - - @ShadeViewRefactor(RefactorComponent.INPUT) - private final DragDownCallback mDragDownCallback = new DragDownCallback() { - - @Override - public boolean canDragDown() { - return mStatusBarState == StatusBarState.KEYGUARD - && (mController.hasActiveNotifications() || mKeyguardMediaControllorVisible) - || mController.isInLockedDownShade(); - } - - /* Only ever called as a consequence of a lockscreen expansion gesture. */ - @Override - public void onDraggedDown(View startingChild, int dragLengthY) { - boolean canDragDown = - mController.hasActiveNotifications() || mKeyguardMediaControllorVisible; - if (mStatusBarState == StatusBarState.KEYGUARD && canDragDown) { - mLockscreenGestureLogger.write( - MetricsEvent.ACTION_LS_SHADE, - (int) (dragLengthY / mDisplayMetrics.density), - 0 /* velocityDp - N/A */); - mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_PULL_SHADE_OPEN); - - if (!mAmbientState.isDozing() || startingChild != null) { - // We have notifications, go to locked shade. - mShadeController.goToLockedShade(startingChild); - if (startingChild instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) startingChild; - row.onExpandedByGesture(true /* drag down is always an open */); - } - } - } else if (mController.isInLockedDownShade()) { - mStatusbarStateController.setLeaveOpenOnKeyguardHide(true); - mStatusBar.dismissKeyguardThenExecute(() -> false /* dismissAction */, - null /* cancelRunnable */, false /* afterKeyguardGone */); - } - } - - @Override - public void onDragDownReset() { - setDimmed(true /* dimmed */, true /* animated */); - resetScrollPosition(); - resetCheckSnoozeLeavebehind(); - } - - @Override - public void onCrossedThreshold(boolean above) { - setDimmed(!above /* dimmed */, true /* animate */); - } - - @Override - public void onTouchSlopExceeded() { - cancelLongPress(); - mController.checkSnoozeLeavebehind(); - } - - @Override - public void setEmptyDragAmount(float amount) { - mNotificationPanelController.setEmptyDragAmount(amount); - } - - @Override - public boolean isFalsingCheckNeeded() { - return mStatusBarState == StatusBarState.KEYGUARD; - } - - @Override - public boolean isDragDownEnabledForView(ExpandableView view) { - if (isDragDownAnywhereEnabled()) { - return true; - } - if (mController.isInLockedDownShade()) { - if (view == null) { - // Dragging down is allowed in general - return true; - } - if (view instanceof ExpandableNotificationRow) { - // Only drag down on sensitive views, otherwise the ExpandHelper will take this - return ((ExpandableNotificationRow) view).getEntry().isSensitive(); - } - } - return false; - } - - @Override - public boolean isDragDownAnywhereEnabled() { - return mStatusbarStateController.getState() == StatusBarState.KEYGUARD - && !mKeyguardBypassEnabledProvider.getBypassEnabled(); - } - }; - - public DragDownCallback getDragDownCallback() { return mDragDownCallback; } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private final HeadsUpTouchHelper.Callback mHeadsUpCallback = new HeadsUpTouchHelper.Callback() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index f7eb574feac3..0d42428dd11d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -31,6 +31,7 @@ import static com.android.systemui.statusbar.notification.stack.NotificationStac import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.canChildBeDismissed; import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Point; import android.graphics.PointF; @@ -38,6 +39,7 @@ import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.Log; +import android.util.MathUtils; import android.util.Pair; import android.view.Display; import android.view.LayoutInflater; @@ -59,6 +61,7 @@ import com.android.systemui.ExpandHelper; import com.android.systemui.Gefingerpoken; import com.android.systemui.R; import com.android.systemui.SwipeHelper; +import com.android.systemui.animation.Interpolators; import com.android.systemui.classifier.Classifier; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.colorextraction.SysuiColorExtractor; @@ -70,6 +73,7 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEv import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.FeatureFlags; +import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -107,7 +111,6 @@ import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; @@ -168,6 +171,7 @@ public class NotificationStackScrollLayoutController { // TODO: StatusBar should be encapsulated behind a Controller private final StatusBar mStatusBar; private final SectionHeaderController mSilentHeaderController; + private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; private NotificationStackScrollLayout mView; private boolean mFadeNotificationsOnDismiss; @@ -181,6 +185,9 @@ public class NotificationStackScrollLayoutController { private ColorExtractor.OnColorsChangedListener mOnColorsChangedListener; + private int mTotalDistanceForFullShadeTransition; + private int mTotalExtraMediaInsetFullShadeTransition; + @VisibleForTesting final View.OnAttachStateChangeListener mOnAttachStateChangeListener = new View.OnAttachStateChangeListener() { @@ -240,8 +247,20 @@ public class NotificationStackScrollLayoutController { public void onThemeChanged() { updateFooter(); } + + @Override + public void onConfigChanged(Configuration newConfig) { + updateResources(); + } }; + private void updateResources() { + mTotalExtraMediaInsetFullShadeTransition = mResources.getDimensionPixelSize( + R.dimen.lockscreen_shade_transition_extra_media_inset); + mTotalDistanceForFullShadeTransition = mResources.getDimensionPixelSize( + R.dimen.lockscreen_shade_qs_transition_distance); + } + private final StatusBarStateController.StateListener mStateListener = new StatusBarStateController.StateListener() { @Override @@ -571,6 +590,7 @@ public class NotificationStackScrollLayoutController { NotifPipeline notifPipeline, NotifCollection notifCollection, NotificationEntryManager notificationEntryManager, + LockscreenShadeTransitionController lockscreenShadeTransitionController, IStatusBarService iStatusBarService, UiEventLogger uiEventLogger, ForegroundServiceDismissalFeatureController fgFeatureController, @@ -592,6 +612,7 @@ public class NotificationStackScrollLayoutController { mZenModeController = zenModeController; mLockscreenUserManager = lockscreenUserManager; mMetricsLogger = metricsLogger; + mLockscreenShadeTransitionController = lockscreenShadeTransitionController; mFalsingCollector = falsingCollector; mFalsingManager = falsingManager; mResources = resources; @@ -624,6 +645,7 @@ public class NotificationStackScrollLayoutController { mRemoteInputManager = remoteInputManager; mVisualStabilityManager = visualStabilityManager; mShadeController = shadeController; + updateResources(); } public void attach(NotificationStackScrollLayout view) { @@ -676,6 +698,8 @@ public class NotificationStackScrollLayoutController { mScrimController.setScrimBehindChangeRunnable(mView::updateBackgroundDimming); + mLockscreenShadeTransitionController.setStackScroller(this); + mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener); mFadeNotificationsOnDismiss = // TODO: this should probably be injected directly @@ -705,7 +729,6 @@ public class NotificationStackScrollLayoutController { Settings.Secure.NOTIFICATION_HISTORY_ENABLED); mKeyguardMediaController.setVisibilityChangedListener(visible -> { - mView.setKeyguardMediaControllorVisible(visible); if (visible) { mView.generateAddAnimation( mKeyguardMediaController.getSinglePaneContainer(), @@ -1203,11 +1226,6 @@ public class NotificationStackScrollLayoutController { mView.runAfterAnimationFinished(r); } - public void setNotificationPanelController( - NotificationPanelViewController notificationPanelViewController) { - mView.setNotificationPanelController(notificationPanelViewController); - } - public void setShelfController(NotificationShelfController notificationShelfController) { mView.setShelfController(notificationShelfController); } @@ -1275,7 +1293,10 @@ public class NotificationStackScrollLayoutController { NotificationLogger.getNotificationLocation(entry))); } - boolean hasActiveNotifications() { + /** + * @return if the shade has currently any active notifications. + */ + public boolean hasActiveNotifications() { if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { return !mNotifPipeline.getShadeList().isEmpty(); } else { @@ -1354,11 +1375,60 @@ public class NotificationStackScrollLayoutController { } } + /** + * @return the expand helper callback. + */ + public ExpandHelper.Callback getExpandHelperCallback() { + return mView.getExpandHelperCallback(); + } + + /** + * @return If the shade is in the locked down shade. + */ public boolean isInLockedDownShade() { return mDynamicPrivacyController.isInLockedDownShade(); } /** + * Set the dimmed state for all of the notification views. + */ + public void setDimmed(boolean dimmed, boolean animate) { + mView.setDimmed(dimmed, animate); + } + + /** + * @return the inset during the full shade transition, that needs to be added to the position + * of the quick settings edge. This is relevant for media, that is transitioning + * from the keyguard host to the quick settings one. + */ + public int getFullShadeTransitionInset() { + MediaHeaderView view = mKeyguardMediaController.getSinglePaneContainer(); + if (view == null || view.getHeight() == 0 + || mStatusBarStateController.getState() != KEYGUARD) { + return 0; + } + return view.getHeight() + mView.getPaddingAfterMedia(); + } + + /** + * Set the amount of pixels we have currently dragged down if we're transitioning to the full + * shade. 0.0f means we're not transitioning yet. + */ + public void setTransitionToFullShadeAmount(float amount) { + float extraTopInset; + MediaHeaderView view = mKeyguardMediaController.getSinglePaneContainer(); + if (view == null || view.getHeight() == 0 + || mStatusBarStateController.getState() != KEYGUARD) { + extraTopInset = 0; + } else { + extraTopInset = MathUtils.saturate(amount / mTotalDistanceForFullShadeTransition); + extraTopInset = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(extraTopInset); + extraTopInset = extraTopInset * mTotalExtraMediaInsetFullShadeTransition; + } + mView.setExtraTopInsetForFullShadeTransition(extraTopInset); + } + + /** * Enum for UiEvent logged from this class */ enum NotificationPanelEvent implements UiEventLogger.UiEventEnum { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index c64b893970c6..ae6a1e52574d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -251,7 +251,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL super.onFinishInflate(); mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext), new ActivityIntentHelper(mContext)); - mPreviewContainer = findViewById(R.id.preview_container); mOverlayContainer = findViewById(R.id.overlay_container); mRightAffordanceView = findViewById(R.id.camera_button); mLeftAffordanceView = findViewById(R.id.left_button); @@ -268,7 +267,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mKeyguardStateController.addCallback(this); setClipChildren(false); setClipToPadding(false); - inflateCameraPreview(); mRightAffordanceView.setOnClickListener(this); mLeftAffordanceView.setOnClickListener(this); initAccessibility(); @@ -276,13 +274,21 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mFlashlightController = Dependency.get(FlashlightController.class); mAccessibilityController = Dependency.get(AccessibilityController.class); mActivityIntentHelper = new ActivityIntentHelper(getContext()); - updateLeftAffordance(); mIndicationPadding = getResources().getDimensionPixelSize( R.dimen.keyguard_indication_area_padding); updateWalletVisibility(); } + /** + * Set the container where the previews are rendered. + */ + public void setPreviewContainer(ViewGroup previewContainer) { + mPreviewContainer = previewContainer; + inflateCameraPreview(); + updateLeftAffordance(); + } + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); @@ -680,6 +686,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } private void inflateCameraPreview() { + if (mPreviewContainer == null) { + return; + } View previewBefore = mCameraPreview; boolean visibleBefore = false; if (previewBefore != null) { @@ -697,6 +706,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } private void updateLeftPreview() { + if (mPreviewContainer == null) { + return; + } View previewBefore = mLeftPreview; if (previewBefore != null) { mPreviewContainer.removeView(previewBefore); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index 069c19770aef..f4710f49524d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -140,7 +140,7 @@ public class KeyguardClockPositionAlgorithm { */ private float mQsExpansion; - private float mEmptyDragAmount; + private float mOverStretchAmount; /** * Setting if bypass is enabled. If true the clock should always be positioned like it's dark @@ -181,7 +181,7 @@ public class KeyguardClockPositionAlgorithm { int notificationStackHeight, float panelExpansion, int parentHeight, int keyguardStatusHeight, int userSwitchHeight, int clockPreferredY, int userSwitchPreferredY, boolean hasCustomClock, boolean hasVisibleNotifs, float dark, - float emptyDragAmount, boolean bypassEnabled, int unlockedStackScrollerPadding, + float overStrechAmount, boolean bypassEnabled, int unlockedStackScrollerPadding, float qsExpansion, int cutoutTopInset, boolean isSplitShade) { mMinTopMargin = keyguardStatusBarHeaderHeight + Math.max(mContainerTopPadding, userSwitchHeight); @@ -196,7 +196,7 @@ public class KeyguardClockPositionAlgorithm { mHasCustomClock = hasCustomClock; mHasVisibleNotifs = hasVisibleNotifs; mDarkAmount = dark; - mEmptyDragAmount = emptyDragAmount; + mOverStretchAmount = overStrechAmount; mBypassEnabled = bypassEnabled; mUnlockedStackScrollerPadding = unlockedStackScrollerPadding; mQsExpansion = qsExpansion; @@ -301,7 +301,7 @@ public class KeyguardClockPositionAlgorithm { } clockYDark = clockY + burnInPreventionOffsetY() + shift; } - return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mEmptyDragAmount); + return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount); } private int getUserSwitcherY(float panelExpansion) { @@ -312,7 +312,7 @@ public class KeyguardClockPositionAlgorithm { float shadeExpansion = Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(panelExpansion); float userSwitchY = MathUtils.lerp(userSwitchYBouncer, userSwitchYRegular, shadeExpansion); - return (int) (userSwitchY + mEmptyDragAmount); + return (int) (userSwitchY + mOverStretchAmount); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 12dec14dabd0..089277070658 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -114,6 +114,7 @@ import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.KeyguardAffordanceView; import com.android.systemui.statusbar.KeyguardIndicationController; +import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShelfController; @@ -207,7 +208,6 @@ public class NotificationPanelViewController extends PanelViewController { private final ConfigurationListener mConfigurationListener = new ConfigurationListener(); @VisibleForTesting final StatusBarStateListener mStatusBarStateListener = new StatusBarStateListener(); - private final ExpansionCallback mExpansionCallback = new ExpansionCallback(); private final BiometricUnlockController mBiometricUnlockController; private final NotificationPanelView mView; private final VibratorHelper mVibratorHelper; @@ -312,10 +312,12 @@ public class NotificationPanelViewController extends PanelViewController { // Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow card. // If there are exactly 1 + mMaxKeyguardNotifications, then still shows all notifications private final int mMaxKeyguardNotifications; + private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; private boolean mShouldUseSplitNotificationShade; // Current max allowed keyguard notifications determined by measuring the panel private int mMaxAllowedKeyguardNotifications; + private ViewGroup mPreviewContainer; private KeyguardAffordanceHelper mAffordanceHelper; private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController; private KeyguardUserSwitcherController mKeyguardUserSwitcherController; @@ -365,7 +367,7 @@ public class NotificationPanelViewController extends PanelViewController { private int mStatusBarMinHeight; private int mStatusBarHeaderHeightKeyguard; private int mNotificationsHeaderCollideDistance; - private float mEmptyDragAmount; + private float mOverStretchAmount; private float mDownX; private float mDownY; private int mDisplayCutoutTopInset = 0; // in pixels @@ -481,7 +483,6 @@ public class NotificationPanelViewController extends PanelViewController { private final CommandQueue mCommandQueue; private final NotificationLockscreenUserManager mLockscreenUserManager; private final UserManager mUserManager; - private final ShadeController mShadeController; private final MediaDataManager mMediaDataManager; private NotificationShadeDepthController mDepthController; private int mDisplayId; @@ -505,6 +506,39 @@ public class NotificationPanelViewController extends PanelViewController { private float mSectionPadding; /** + * The amount of progress we are currently in if we're transitioning to the full shade. + * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full + * shade. This value can also go beyond 1.1 when we're overshooting! + */ + private float mTransitioningToFullShadeProgress; + + /** + * Position of the qs bottom during the full shade transition. This is needed as the toppadding + * can change during state changes, which makes it much harder to do animations + */ + private int mTransitionToFullShadeQSPosition; + + /** + * Distance that the full shade transition takes in order for qs to fully transition to the + * shade. + */ + private int mDistanceForQSFullShadeTransition; + + /** + * The maximum overshoot allowed for the top padding for the full shade transition + */ + private int mMaxOverscrollAmountForDragDown; + + /** + * Should we animate the next bounds update + */ + private boolean mAnimateNextNotificationBounds; + /** + * The delay for the next bounds animation + */ + private long mNotificationBoundsAnimationDelay; + + /** * Is this a collapse that started on the panel where we should allow the panel to intercept */ private boolean mIsPanelCollapseOnQQS; @@ -521,6 +555,16 @@ public class NotificationPanelViewController extends PanelViewController { private boolean mDelayShowingKeyguardStatusBar; private boolean mAnimatingQS; + + /** + * The end bounds of a clipping animation. + */ + private final Rect mQsClippingAnimationEndBounds = new Rect(); + + /** + * The animator for the qs clipping bounds. + */ + private ValueAnimator mQsClippingAnimation = null; private final Rect mKeyguardStatusAreaClipBounds = new Rect(); private int mOldLayoutDirection; private NotificationShelfController mNotificationShelfController; @@ -569,7 +613,7 @@ public class NotificationPanelViewController extends PanelViewController { NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler, DynamicPrivacyController dynamicPrivacyController, KeyguardBypassController bypassController, FalsingManager falsingManager, - FalsingCollector falsingCollector, ShadeController shadeController, + FalsingCollector falsingCollector, NotificationLockscreenUserManager notificationLockscreenUserManager, NotificationEntryManager notificationEntryManager, KeyguardStateController keyguardStateController, @@ -591,6 +635,7 @@ public class NotificationPanelViewController extends PanelViewController { KeyguardQsUserSwitchComponent.Factory keyguardQsUserSwitchComponentFactory, KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory, KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory, + LockscreenShadeTransitionController lockscreenShadeTransitionController, QSDetailDisplayer qsDetailDisplayer, NotificationGroupManagerLegacy groupManager, NotificationIconAreaController notificationIconAreaController, @@ -677,6 +722,8 @@ public class NotificationPanelViewController extends PanelViewController { } } }; + mLockscreenShadeTransitionController = lockscreenShadeTransitionController; + lockscreenShadeTransitionController.setNotificationPanelController(this); mKeyguardStateController.addCallback(keyguardMonitorCallback); DynamicPrivacyControlListener dynamicPrivacyControlListener = @@ -690,7 +737,6 @@ public class NotificationPanelViewController extends PanelViewController { }); mBottomAreaShadeAlphaAnimator.setDuration(160); mBottomAreaShadeAlphaAnimator.setInterpolator(Interpolators.ALPHA_OUT); - mShadeController = shadeController; mLockscreenUserManager = notificationLockscreenUserManager; mEntryManager = notificationEntryManager; mConversationNotificationManager = conversationNotificationManager; @@ -749,14 +795,22 @@ public class NotificationPanelViewController extends PanelViewController { mOnEmptySpaceClickListener); addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp); mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area); + mPreviewContainer = mView.findViewById(R.id.preview_container); + mKeyguardBottomArea.setPreviewContainer(mPreviewContainer); mLastOrientation = mResources.getConfiguration().orientation; initBottomArea(); mWakeUpCoordinator.setStackScroller(mNotificationStackScrollLayoutController); mQsFrame = mView.findViewById(R.id.qs_frame); - mPulseExpansionHandler.setUp( - mNotificationStackScrollLayoutController, mExpansionCallback, mShadeController); + mPulseExpansionHandler.setUp(mNotificationStackScrollLayoutController, + amount -> { + float progress = amount / mView.getHeight(); + float overstretch = Interpolators.getOvershootInterpolation(progress, + (float) mMaxOverscrollAmountForDragDown / mView.getHeight(), + 0.2f); + setOverStrechAmount(overstretch); + }); mWakeUpCoordinator.addListener(new NotificationWakeUpCoordinator.WakeUpListener() { @Override public void onFullyHiddenChanged(boolean isFullyHidden) { @@ -812,6 +866,10 @@ public class NotificationPanelViewController extends PanelViewController { com.android.internal.R.dimen.status_bar_height); mHeadsUpInset = statusbarHeight + mResources.getDimensionPixelSize( R.dimen.heads_up_status_bar_padding); + mDistanceForQSFullShadeTransition = mResources.getDimensionPixelSize( + R.dimen.lockscreen_shade_qs_transition_distance); + mMaxOverscrollAmountForDragDown = mResources.getDimensionPixelSize( + R.dimen.lockscreen_shade_max_top_overshoot); mScrimCornerRadius = mResources.getDimensionPixelSize( R.dimen.notification_scrim_corner_radius); mScreenCornerRadius = mResources.getDimensionPixelSize( @@ -989,6 +1047,7 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardBottomArea = (KeyguardBottomAreaView) mLayoutInflater.inflate( R.layout.keyguard_bottom_area, mView, false); mKeyguardBottomArea.initFrom(oldBottomArea); + mKeyguardBottomArea.setPreviewContainer(mPreviewContainer); mView.addView(mKeyguardBottomArea, index); initBottomArea(); mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea); @@ -1108,58 +1167,28 @@ public class NotificationPanelViewController extends PanelViewController { * showing. */ private void positionClockAndNotifications() { + positionClockAndNotifications(false /* forceUpdate */); + } + + /** + * Positions the clock and notifications dynamically depending on how many notifications are + * showing. + * + * @param forceClockUpdate Should the clock be updated even when not on keyguard + */ + private void positionClockAndNotifications(boolean forceClockUpdate) { boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending(); - boolean animateClock = animate || mAnimateNextPositionUpdate; int stackScrollerPadding; - if (mBarState != KEYGUARD) { + boolean onKeyguard = isOnKeyguard(); + if (onKeyguard || forceClockUpdate) { + updateClockAppearance(); + } + if (!onKeyguard) { stackScrollerPadding = getUnlockedStackScrollerPadding(); } else { - int totalHeight = mView.getHeight(); - int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding); - int clockPreferredY = mKeyguardStatusViewController.getClockPreferredY(totalHeight); - int userSwitcherPreferredY = mStatusBarHeaderHeightKeyguard; - boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled(); - final boolean hasVisibleNotifications = mNotificationStackScrollLayoutController - .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia(); - mKeyguardStatusViewController.setHasVisibleNotifications(hasVisibleNotifications); - int userIconHeight = mKeyguardQsUserSwitchController != null - ? mKeyguardQsUserSwitchController.getUserIconHeight() : 0; - mClockPositionAlgorithm.setup(mStatusBarHeaderHeightKeyguard, - totalHeight - bottomPadding, - mNotificationStackScrollLayoutController.getIntrinsicContentHeight(), - getExpandedFraction(), - totalHeight, - mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1 - ? mKeyguardStatusViewController.getHeight() - : (int) (mKeyguardStatusViewController.getHeight() - - mShelfHeight / 2.0f - mDarkIconSize / 2.0f), - userIconHeight, - clockPreferredY, userSwitcherPreferredY, hasCustomClock(), - hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount, - bypassEnabled, getUnlockedStackScrollerPadding(), - getQsExpansionFraction(), - mDisplayCutoutTopInset, - shouldUseSplitNotificationShade(mFeatureFlags, mResources)); - mClockPositionAlgorithm.run(mClockPositionResult); - mKeyguardStatusViewController.updatePosition( - mClockPositionResult.clockX, mClockPositionResult.clockY, - mClockPositionResult.clockScale, animateClock); - if (mKeyguardQsUserSwitchController != null) { - mKeyguardQsUserSwitchController.updatePosition( - mClockPositionResult.clockX, - mClockPositionResult.userSwitchY, - animateClock); - } - if (mKeyguardUserSwitcherController != null) { - mKeyguardUserSwitcherController.updatePosition( - mClockPositionResult.clockX, - mClockPositionResult.userSwitchY, - animateClock); - } - updateNotificationTranslucency(); - updateClock(); stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded; } + mNotificationStackScrollLayoutController.setIntrinsicPadding(stackScrollerPadding); mKeyguardBottomArea.setAntiBurnInOffsetX(mClockPositionResult.clockX); @@ -1169,6 +1198,60 @@ public class NotificationPanelViewController extends PanelViewController { mAnimateNextPositionUpdate = false; } + private void updateClockAppearance() { + int totalHeight = mView.getHeight(); + int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding); + int clockPreferredY = mKeyguardStatusViewController.getClockPreferredY(totalHeight); + int userSwitcherPreferredY = mStatusBarHeaderHeightKeyguard; + boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled(); + final boolean hasVisibleNotifications = mNotificationStackScrollLayoutController + .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia(); + mKeyguardStatusViewController.setHasVisibleNotifications(hasVisibleNotifications); + int userIconHeight = mKeyguardQsUserSwitchController != null + ? mKeyguardQsUserSwitchController.getUserIconHeight() : 0; + float expandedFraction = + mKeyguardStatusViewController.isAnimatingScreenOffFromUnlocked() ? 1.0f + : getExpandedFraction(); + float darkamount = mKeyguardStatusViewController.isAnimatingScreenOffFromUnlocked() ? 1.0f + : mInterpolatedDarkAmount; + mClockPositionAlgorithm.setup(mStatusBarHeaderHeightKeyguard, + totalHeight - bottomPadding, + mNotificationStackScrollLayoutController.getIntrinsicContentHeight(), + expandedFraction, + totalHeight, + mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1 + ? mKeyguardStatusViewController.getHeight() + : (int) (mKeyguardStatusViewController.getHeight() + - mShelfHeight / 2.0f - mDarkIconSize / 2.0f), + userIconHeight, + clockPreferredY, userSwitcherPreferredY, hasCustomClock(), + hasVisibleNotifications, darkamount, mOverStretchAmount, + bypassEnabled, getUnlockedStackScrollerPadding(), + computeQsExpansionFraction(), + mDisplayCutoutTopInset, + shouldUseSplitNotificationShade(mFeatureFlags, mResources)); + mClockPositionAlgorithm.run(mClockPositionResult); + boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending(); + boolean animateClock = animate || mAnimateNextPositionUpdate; + mKeyguardStatusViewController.updatePosition( + mClockPositionResult.clockX, mClockPositionResult.clockY, + mClockPositionResult.clockScale, animateClock); + if (mKeyguardQsUserSwitchController != null) { + mKeyguardQsUserSwitchController.updatePosition( + mClockPositionResult.clockX, + mClockPositionResult.userSwitchY, + animateClock); + } + if (mKeyguardUserSwitcherController != null) { + mKeyguardUserSwitcherController.updatePosition( + mClockPositionResult.clockX, + mClockPositionResult.userSwitchY, + animateClock); + } + updateNotificationTranslucency(); + updateClock(); + } + /** * @return the padding of the stackscroller when unlocked */ @@ -1600,7 +1683,7 @@ public class NotificationPanelViewController extends PanelViewController { private boolean flingExpandsQs(float vel) { if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { - return getQsExpansionFraction() > 0.5f; + return computeQsExpansionFraction() > 0.5f; } else { return vel > 0; } @@ -1613,7 +1696,7 @@ public class NotificationPanelViewController extends PanelViewController { return !mQsTouchAboveFalsingThreshold; } - private float getQsExpansionFraction() { + private float computeQsExpansionFraction() { return Math.min( 1f, (mQsExpansionHeight - mQsMinExpansionHeight) / (mQsMaxExpansionHeight - mQsMinExpansionHeight)); @@ -1838,7 +1921,7 @@ public class NotificationPanelViewController extends PanelViewController { mQsTracking = false; mTrackingPointer = -1; trackMovement(event); - float fraction = getQsExpansionFraction(); + float fraction = computeQsExpansionFraction(); if (fraction != 0f || y >= mInitialTouchY) { flingQsWithCurrentVelocity(y, event.getActionMasked() == MotionEvent.ACTION_CANCEL); @@ -2044,18 +2127,19 @@ public class NotificationPanelViewController extends PanelViewController { protected void updateQsExpansion() { if (mQs == null) return; - float qsExpansionFraction = getQsExpansionFraction(); + float qsExpansionFraction = computeQsExpansionFraction(); mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation()); mMediaHierarchyManager.setQsExpansion(qsExpansionFraction); int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction); mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY); + setQSClippingBounds(); mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction); mDepthController.setQsPanelExpansion(qsExpansionFraction); } private Runnable mOnStackYChanged = () -> { if (mQs != null) { - setNotificationBounds(); + setQSClippingBounds(); } }; @@ -2063,57 +2147,121 @@ public class NotificationPanelViewController extends PanelViewController { * Updates scrim bounds, QS clipping, and KSV clipping as well based on the bounds of the shade * and QS state. */ - private void setNotificationBounds() { + private void setQSClippingBounds() { int top = 0; int bottom = 0; int left = 0; int right = 0; - final int qsPanelBottomY = calculateQsBottomPosition(getQsExpansionFraction()); - final boolean visible = (getQsExpansionFraction() > 0 || qsPanelBottomY > 0) + final int qsPanelBottomY = calculateQsBottomPosition(computeQsExpansionFraction()); + final boolean visible = (computeQsExpansionFraction() > 0 || qsPanelBottomY > 0) && !mShouldUseSplitNotificationShade; - final float notificationTop = mAmbientState.getStackY() - mAmbientState.getScrollY(); setQsExpansionEnabled(mAmbientState.getScrollY() == 0); - int radius = mScrimCornerRadius; if (!mShouldUseSplitNotificationShade) { - top = (int) (isOnKeyguard() ? Math.min(qsPanelBottomY, notificationTop) - : notificationTop); + if (mTransitioningToFullShadeProgress > 0.0f) { + // If we're transitioning, let's use the actual value. The else case + // can be wrong during transitions when waiting for the keyguard to unlock + top = mTransitionToFullShadeQSPosition; + } else { + float notificationTop = getQSEdgePosition(); + top = (int) (isOnKeyguard() ? Math.min(qsPanelBottomY, notificationTop) + : notificationTop); + } bottom = getView().getBottom(); left = getView().getLeft(); right = getView().getRight(); - radius = (int) MathUtils.lerp(mScreenCornerRadius, mScrimCornerRadius, - Math.min(top / (float) mScrimCornerRadius, 1f)); } else if (qsPanelBottomY > 0) { // so bounds are empty on lockscreen top = Math.min(qsPanelBottomY, mSplitShadeNotificationsTopPadding); bottom = mNotificationStackScrollLayoutController.getHeight(); left = mNotificationStackScrollLayoutController.getLeft(); right = mNotificationStackScrollLayoutController.getRight(); } + applyQSClippingBounds(left, top, right, bottom, visible); + } - // Fancy clipping for quick settings - if (mQs != null) { - mQs.setFancyClipping(top, bottom, radius, visible); + private void applyQSClippingBounds(int left, int top, int right, int bottom, + boolean visible) { + if (!mAnimateNextNotificationBounds || mKeyguardStatusAreaClipBounds.isEmpty()) { + if (mQsClippingAnimation != null) { + // update the end position of the animator + mQsClippingAnimationEndBounds.set(left, top, right, bottom); + } else { + applyQSClippingImmediately(left, top, right, bottom, visible); + } + } else { + mQsClippingAnimationEndBounds.set(left, top, right, bottom); + final int startLeft = mKeyguardStatusAreaClipBounds.left; + final int startTop = mKeyguardStatusAreaClipBounds.top; + final int startRight = mKeyguardStatusAreaClipBounds.right; + final int startBottom = mKeyguardStatusAreaClipBounds.bottom; + mQsClippingAnimation = ValueAnimator.ofFloat(0.0f, 1.0f); + mQsClippingAnimation.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + mQsClippingAnimation.setDuration( + StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE); + mQsClippingAnimation.setStartDelay(mNotificationBoundsAnimationDelay); + mQsClippingAnimation.addUpdateListener(animation -> { + float fraction = animation.getAnimatedFraction(); + int animLeft = (int) MathUtils.lerp(startLeft, + mQsClippingAnimationEndBounds.left, fraction); + int animTop = (int) MathUtils.lerp(startTop, + mQsClippingAnimationEndBounds.top, fraction); + int animRight = (int) MathUtils.lerp(startRight, + mQsClippingAnimationEndBounds.right, fraction); + int animBottom = (int) MathUtils.lerp(startBottom, + mQsClippingAnimationEndBounds.bottom, fraction); + applyQSClippingImmediately(animLeft, animTop, animRight, animBottom, + visible /* visible */); + }); + mQsClippingAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mQsClippingAnimation = null; + } + }); + mQsClippingAnimation.start(); } + mAnimateNextNotificationBounds = false; + mNotificationBoundsAnimationDelay = 0; + } + + private void applyQSClippingImmediately(int left, int top, int right, int bottom, + boolean visible) { + // Fancy clipping for quick settings + int radius = mScrimCornerRadius; if (!mShouldUseSplitNotificationShade) { // The padding on this area is large enough that we can use a cheaper clipping strategy mKeyguardStatusAreaClipBounds.set(left, top, right, bottom); mKeyguardStatusViewController.setClipBounds(visible ? mKeyguardStatusAreaClipBounds : null); + radius = (int) MathUtils.lerp(mScreenCornerRadius, mScrimCornerRadius, + Math.min(top / (float) mScrimCornerRadius, 1f)); + } + if (mQs != null) { + mQs.setFancyClipping(top, bottom, radius, visible); } mScrimController.setNotificationsBounds(left, top, right, bottom); mScrimController.setScrimCornerRadius(radius); } + private float getQSEdgePosition() { + // TODO: replace StackY with unified calculation + return mAmbientState.getStackY() - mAmbientState.getScrollY(); + } + private int calculateQsBottomPosition(float qsExpansionFraction) { - int qsBottomY = (int) getHeaderTranslation() + mQs.getQsMinExpansionHeight(); - if (qsExpansionFraction != 0.0) { - qsBottomY = (int) MathUtils.lerp( - qsBottomY, mQs.getDesiredHeight(), qsExpansionFraction); + if (mTransitioningToFullShadeProgress > 0.0f) { + return mTransitionToFullShadeQSPosition; + } else { + int qsBottomY = (int) getHeaderTranslation() + mQs.getQsMinExpansionHeight(); + if (qsExpansionFraction != 0.0) { + qsBottomY = (int) MathUtils.lerp( + qsBottomY, mQs.getDesiredHeight(), qsExpansionFraction); + } + // to account for shade overshooting animation, see setSectionPadding method + if (mSectionPadding > 0) qsBottomY += mSectionPadding; + return qsBottomY; } - // to account for shade overshooting animation, see setSectionPadding method - if (mSectionPadding > 0) qsBottomY += mSectionPadding; - return qsBottomY; } private String determineAccessibilityPaneTitle() { @@ -2157,7 +2305,7 @@ public class NotificationPanelViewController extends PanelViewController { // from a scrolled quick settings. return MathUtils.lerp((float) getKeyguardNotificationStaticPadding(), (float) (mQsMaxExpansionHeight + mQsNotificationTopPadding), - getQsExpansionFraction()); + computeQsExpansionFraction()); } else { return mQsExpansionHeight + mQsNotificationTopPadding; } @@ -2194,15 +2342,65 @@ public class NotificationPanelViewController extends PanelViewController { } } - private void updateQSPulseExpansion() { if (mQs != null) { - mQs.setShowCollapsedOnKeyguard( + mQs.setPulseExpanding( mKeyguardShowing && mKeyguardBypassController.getBypassEnabled() && mNotificationStackScrollLayoutController.isPulseExpanding()); } } + /** + * Set the amount of pixels we have currently dragged down if we're transitioning to the full + * shade. 0.0f means we're not transitioning yet. + */ + public void setTransitionToFullShadeAmount(float pxAmount, boolean animate, long delay) { + mAnimateNextNotificationBounds = animate && !mShouldUseSplitNotificationShade; + mNotificationBoundsAnimationDelay = delay; + float progress = MathUtils.saturate(pxAmount / mView.getHeight()); + + float endPosition = 0; + if (pxAmount > 0.0f) { + if (mNotificationStackScrollLayoutController.getVisibleNotificationCount() == 0 + && !mMediaDataManager.hasActiveMedia()) { + // No notifications are visible, let's animate to the height of qs instead + if (mQs != null) { + // Let's interpolate to the header height + endPosition = mQs.getHeader().getHeight(); + } + } else { + // Interpolating to the new bottom edge position! + endPosition = getQSEdgePosition() - mOverStretchAmount; + + // If we have media, we need to put the boundary below it, as the media header + // still uses the space during the transition. + endPosition += + mNotificationStackScrollLayoutController.getFullShadeTransitionInset(); + } + } + + // Calculate the overshoot amount such that we're reaching the target after our desired + // distance, but only reach it fully once we drag a full shade length. + float transitionProgress = 0; + if (endPosition != 0 && progress != 0) { + transitionProgress = Interpolators.getOvershootInterpolation(progress, + mMaxOverscrollAmountForDragDown / endPosition, + (float) mDistanceForQSFullShadeTransition / (float) mView.getHeight()); + } + mTransitioningToFullShadeProgress = transitionProgress; + + int position = (int) MathUtils.lerp((float) 0, endPosition, + mTransitioningToFullShadeProgress); + if (mTransitioningToFullShadeProgress > 0.0f) { + // we want at least 1 pixel otherwise the panel won't be clipped + position = Math.max(1, position); + } + float overStretchAmount = Math.max(position - endPosition, 0.0f); + setOverStrechAmount(overStretchAmount); + mTransitionToFullShadeQSPosition = position; + updateQsExpansion(); + } + private void trackMovement(MotionEvent event) { if (mQsVelocityTracker != null) mQsVelocityTracker.addMovement(event); } @@ -2626,7 +2824,7 @@ public class NotificationPanelViewController extends PanelViewController { if (!mKeyguardShowing) { return; } - float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2); + float alphaQsExpansion = 1 - Math.min(1, computeQsExpansionFraction() * 2); float newAlpha = Math.min(getKeyguardContentsAlpha(), alphaQsExpansion) * mKeyguardStatusBarAnimateAlpha; newAlpha *= 1.0f - mKeyguardHeadsUpShowingAmount; @@ -2649,7 +2847,7 @@ public class NotificationPanelViewController extends PanelViewController { float expansionAlpha = MathUtils.map( isUnlockHintRunning() ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f, getExpandedFraction()); - float alpha = Math.min(expansionAlpha, 1 - getQsExpansionFraction()); + float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction()); alpha *= mBottomAreaShadeAlpha; mKeyguardBottomArea.setAffordanceAlpha(alpha); mKeyguardBottomArea.setImportantForAccessibility( @@ -2671,7 +2869,7 @@ public class NotificationPanelViewController extends PanelViewController { float expansionAlpha = MathUtils.map( isUnlockHintRunning() ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f, getExpandedFraction()); - float alpha = Math.min(expansionAlpha, 1 - getQsExpansionFraction()); + float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction()); mBigClockContainer.setAlpha(alpha); } @@ -3223,6 +3421,7 @@ public class NotificationPanelViewController extends PanelViewController { mHeightListener.onQsHeightChanged(); } }); + mLockscreenShadeTransitionController.setQS(mQs); mNotificationStackScrollLayoutController.setQsContainer((ViewGroup) mQs.getView()); updateQsExpansion(); } @@ -3460,9 +3659,9 @@ public class NotificationPanelViewController extends PanelViewController { StatusBar statusBar, NotificationShelfController notificationShelfController) { setStatusBar(statusBar); - mNotificationStackScrollLayoutController.setNotificationPanelController(this); mNotificationStackScrollLayoutController.setShelfController(notificationShelfController); mNotificationShelfController = notificationShelfController; + mLockscreenShadeTransitionController.bindController(notificationShelfController); updateMaxDisplayedNotifications(true); } @@ -3513,10 +3712,6 @@ public class NotificationPanelViewController extends PanelViewController { return new OnLayoutChangeListener(); } - public void setEmptyDragAmount(float amount) { - mExpansionCallback.setEmptyDragAmount(amount); - } - @Override protected TouchHandler createTouchHandler() { return new TouchHandler() { @@ -3985,7 +4180,7 @@ public class NotificationPanelViewController extends PanelViewController { mClockPositionResult.clockX, mClockPositionResult.clockYFullyDozing, mClockPositionResult.clockScale, - false); + false /* animate */); } mKeyguardStatusViewController.setKeyguardStatusViewVisibility( @@ -4001,11 +4196,7 @@ public class NotificationPanelViewController extends PanelViewController { if (oldState == KEYGUARD && (goingToFullShade || statusBarState == StatusBarState.SHADE_LOCKED)) { animateKeyguardStatusBarOut(); - long - delay = - mBarState == StatusBarState.SHADE_LOCKED ? 0 - : mKeyguardStateController.calculateGoingToFullShadeDelay(); - mQs.animateHeaderSlidingIn(delay); + updateQSMinHeight(); } else if (oldState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) { animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD); @@ -4052,11 +4243,12 @@ public class NotificationPanelViewController extends PanelViewController { } } - private class ExpansionCallback implements PulseExpansionHandler.ExpansionCallback { - public void setEmptyDragAmount(float amount) { - mEmptyDragAmount = amount * 0.2f; - positionClockAndNotifications(); - } + /** + * Sets the overstretch amount in raw pixels when dragging down. + */ + public void setOverStrechAmount(float amount) { + mOverStretchAmount = amount; + positionClockAndNotifications(true /* forceUpdate */); } private class OnAttachStateChangeListener implements View.OnAttachStateChangeListener { @@ -4103,11 +4295,7 @@ public class NotificationPanelViewController extends PanelViewController { // Calculate quick setting heights. int oldMaxHeight = mQsMaxExpansionHeight; if (mQs != null) { - float previousMin = mQsMinExpansionHeight; - mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight(); - if (mQsExpansionHeight == previousMin) { - mQsExpansionHeight = mQsMinExpansionHeight; - } + updateQSMinHeight(); mQsMaxExpansionHeight = mQs.getDesiredHeight(); mNotificationStackScrollLayoutController.setMaxTopPadding( mQsMaxExpansionHeight + mQsNotificationTopPadding); @@ -4149,6 +4337,14 @@ public class NotificationPanelViewController extends PanelViewController { } } + private void updateQSMinHeight() { + float previousMin = mQsMinExpansionHeight; + mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight(); + if (mQsExpansionHeight == previousMin) { + mQsExpansionHeight = mQsMinExpansionHeight; + } + } + private class DebugDrawable extends Drawable { @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java index f19cf1bed034..7f4dabd3f59f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java @@ -34,15 +34,14 @@ import android.view.View; import android.view.ViewGroup; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.ExpandHelper; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dock.DockManager; import com.android.systemui.doze.DozeLog; -import com.android.systemui.plugins.FalsingManager; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DragDownHelper; +import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; @@ -73,7 +72,6 @@ public class NotificationShadeWindowViewController { private final DynamicPrivacyController mDynamicPrivacyController; private final KeyguardBypassController mBypassController; private final PluginManager mPluginManager; - private final FalsingManager mFalsingManager; private final FalsingCollector mFalsingCollector; private final TunerService mTunerService; private final NotificationLockscreenUserManager mNotificationLockscreenUserManager; @@ -87,6 +85,7 @@ public class NotificationShadeWindowViewController { private final ShadeController mShadeController; private final NotificationShadeDepthController mDepthController; private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; + private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private GestureDetector mGestureDetector; @@ -119,7 +118,7 @@ public class NotificationShadeWindowViewController { PulseExpansionHandler pulseExpansionHandler, DynamicPrivacyController dynamicPrivacyController, KeyguardBypassController bypassController, - FalsingManager falsingManager, + LockscreenShadeTransitionController transitionController, FalsingCollector falsingCollector, PluginManager pluginManager, TunerService tunerService, @@ -143,7 +142,7 @@ public class NotificationShadeWindowViewController { mPulseExpansionHandler = pulseExpansionHandler; mDynamicPrivacyController = dynamicPrivacyController; mBypassController = bypassController; - mFalsingManager = falsingManager; + mLockscreenShadeTransitionController = transitionController; mFalsingCollector = falsingCollector; mPluginManager = pluginManager; mTunerService = tunerService; @@ -406,11 +405,7 @@ public class NotificationShadeWindowViewController { } }); - ExpandHelper.Callback expandHelperCallback = mStackScrollLayout.getExpandHelperCallback(); - DragDownHelper.DragDownCallback dragDownCallback = mStackScrollLayout.getDragDownCallback(); - setDragDownHelper( - new DragDownHelper(mFalsingManager, expandHelperCallback, dragDownCallback, - mFalsingCollector, mView, mView.getContext())); + setDragDownHelper(mLockscreenShadeTransitionController.getTouchHelper()); mDepthController.setRoot(mView); mNotificationPanelViewController.addExpansionListener(mDepthController); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index c34fa2f049e1..bbde3c3e3144 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -28,6 +28,7 @@ import android.os.Handler; import android.os.Trace; import android.util.Log; import android.util.MathUtils; +import android.util.Pair; import android.view.View; import android.view.ViewTreeObserver; import android.view.animation.DecelerateInterpolator; @@ -42,10 +43,10 @@ import com.android.internal.util.function.TriConsumer; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.settingslib.Utils; -import com.android.systemui.animation.Interpolators; import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; import com.android.systemui.R; +import com.android.systemui.animation.Interpolators; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dock.DockManager; @@ -98,6 +99,18 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump public static final int OPAQUE = 2; private boolean mClipsQsScrim; + /** + * The amount of progress we are currently in if we're transitioning to the full shade. + * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full + * shade. + */ + private float mTransitionToFullShadeProgress; + + /** + * If we're currently transitioning to the full shade. + */ + private boolean mTransitioningToFullShade; + @IntDef(prefix = {"VISIBILITY_"}, value = { TRANSPARENT, SEMI_TRANSPARENT, @@ -357,7 +370,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump + mInFrontAlpha + ", back: " + mBehindAlpha + ", notif: " + mNotificationsAlpha); } - applyExpansionToAlpha(); + applyStateToAlpha(); // Scrim might acquire focus when user is navigating with a D-pad or a keyboard. // We need to disable focus otherwise AOD would end up with a gray overlay. @@ -499,10 +512,37 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump if (!(relevantState && mExpansionAffectsAlpha)) { return; } - applyAndDispatchExpansion(); + applyAndDispatchState(); + } + } + + /** + * Set the amount of progress we are currently in if we're transitioning to the full shade. + * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full + * shade. + */ + public void setTransitionToFullShadeProgress(float progress) { + if (progress != mTransitionToFullShadeProgress) { + mTransitionToFullShadeProgress = progress; + setTransitionToFullShade(progress > 0.0f); + applyAndDispatchState(); + } + } + + /** + * Set if we're currently transitioning to the full shade + */ + private void setTransitionToFullShade(boolean transitioning) { + if (transitioning != mTransitioningToFullShade) { + mTransitioningToFullShade = transitioning; + if (transitioning) { + // Let's make sure the shade locked is ready + ScrimState.SHADE_LOCKED.prepare(mState); + } } } + /** * Set bounds for notifications background, all coordinates are absolute */ @@ -534,7 +574,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump if (!(relevantState && mExpansionAffectsAlpha)) { return; } - applyAndDispatchExpansion(); + applyAndDispatchState(); } } @@ -553,6 +593,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump if (mScrimBehind != null) { mScrimBehind.enableBottomEdgeConcave(mClipsQsScrim); } + if (mState != ScrimState.UNINITIALIZED) { + // the clipScrimState has changed, let's reprepare ourselves + mState.prepare(mState); + applyAndDispatchState(); + } } @VisibleForTesting @@ -583,7 +628,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } } - private void applyExpansionToAlpha() { + private void applyStateToAlpha() { if (!mExpansionAffectsAlpha) { return; } @@ -608,47 +653,40 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mInFrontAlpha = 0; } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED || mState == ScrimState.PULSING) { - // Either darken of make the scrim transparent when you - // pull down the shade - float interpolatedFract = getInterpolatedFraction(); - float stateBehind = mClipsQsScrim ? mState.getNotifAlpha() : mState.getBehindAlpha(); - float backAlpha; - if (mDarkenWhileDragging) { - backAlpha = MathUtils.lerp(mDefaultScrimAlpha, stateBehind, - interpolatedFract); - } else { - backAlpha = MathUtils.lerp(0 /* start */, stateBehind, - interpolatedFract); + Pair<Integer, Float> result = calculateBackStateForState(mState); + int behindTint = result.first; + float behindAlpha = result.second; + if (mTransitionToFullShadeProgress > 0.0f) { + Pair<Integer, Float> shadeResult = calculateBackStateForState( + ScrimState.SHADE_LOCKED); + behindAlpha = MathUtils.lerp(behindAlpha, shadeResult.second, + mTransitionToFullShadeProgress); + behindTint = ColorUtils.blendARGB(behindTint, shadeResult.first, + mTransitionToFullShadeProgress); } mInFrontAlpha = mState.getFrontAlpha(); - int backTint; if (mClipsQsScrim) { - backTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(), - mState.getNotifTint(), interpolatedFract); - } else { - backTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(), - mState.getBehindTint(), interpolatedFract); - } - if (mQsExpansion > 0) { - backAlpha = MathUtils.lerp(backAlpha, mDefaultScrimAlpha, mQsExpansion); - int stateTint = mClipsQsScrim ? ScrimState.SHADE_LOCKED.getNotifTint() - : ScrimState.SHADE_LOCKED.getBehindTint(); - backTint = ColorUtils.blendARGB(backTint, stateTint, mQsExpansion); - } - if (mClipsQsScrim) { - mNotificationsAlpha = backAlpha; - mNotificationsTint = backTint; + mNotificationsAlpha = behindAlpha; + mNotificationsTint = behindTint; mBehindAlpha = 1; mBehindTint = Color.BLACK; } else { - mBehindAlpha = backAlpha; + mBehindAlpha = behindAlpha; if (mState == ScrimState.SHADE_LOCKED) { // going from KEYGUARD to SHADE_LOCKED state mNotificationsAlpha = getInterpolatedFraction(); } else { mNotificationsAlpha = Math.max(1.0f - getInterpolatedFraction(), mQsExpansion); } - mBehindTint = backTint; + if (mState == ScrimState.KEYGUARD && mTransitionToFullShadeProgress > 0.0f) { + // Interpolate the notification alpha when transitioning! + mNotificationsAlpha = MathUtils.lerp( + mNotificationsAlpha, + getInterpolatedFraction(), + mTransitionToFullShadeProgress); + } + mNotificationsTint = mState.getNotifTint(); + mBehindTint = behindTint; } } if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) { @@ -658,8 +696,39 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } } - private void applyAndDispatchExpansion() { - applyExpansionToAlpha(); + private Pair<Integer, Float> calculateBackStateForState(ScrimState state) { + // Either darken of make the scrim transparent when you + // pull down the shade + float interpolatedFract = getInterpolatedFraction(); + float stateBehind = mClipsQsScrim ? state.getNotifAlpha() : state.getBehindAlpha(); + float behindAlpha; + int behindTint; + if (mDarkenWhileDragging) { + behindAlpha = MathUtils.lerp(mDefaultScrimAlpha, stateBehind, + interpolatedFract); + } else { + behindAlpha = MathUtils.lerp(0 /* start */, stateBehind, + interpolatedFract); + } + if (mClipsQsScrim) { + behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(), + state.getNotifTint(), interpolatedFract); + } else { + behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(), + state.getBehindTint(), interpolatedFract); + } + if (mQsExpansion > 0) { + behindAlpha = MathUtils.lerp(behindAlpha, mDefaultScrimAlpha, mQsExpansion); + int stateTint = mClipsQsScrim ? ScrimState.SHADE_LOCKED.getNotifTint() + : ScrimState.SHADE_LOCKED.getBehindTint(); + behindTint = ColorUtils.blendARGB(behindTint, stateTint, mQsExpansion); + } + return new Pair<>(behindTint, behindAlpha); + } + + + private void applyAndDispatchState() { + applyStateToAlpha(); if (mUpdatePending) { return; } @@ -1195,7 +1264,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump public void setExpansionAffectsAlpha(boolean expansionAffectsAlpha) { mExpansionAffectsAlpha = expansionAffectsAlpha; if (expansionAffectsAlpha) { - applyAndDispatchExpansion(); + applyAndDispatchState(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java index 2fa6795e9df0..2d41e5e9be2c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java @@ -14,8 +14,6 @@ package com.android.systemui.statusbar.phone; -import android.view.View; - import com.android.systemui.statusbar.StatusBarState; /** @@ -78,15 +76,6 @@ public interface ShadeController { void runPostCollapseRunnables(); /** - * If secure with redaction: Show bouncer, go to unlocked shade. - * - * <p>If secure without redaction or no security: Go to {@link StatusBarState#SHADE_LOCKED}.</p> - * - * @param startingChild The view to expand after going to the shade. - */ - void goToLockedShade(View startingChild); - - /** * Close the shade if it was open * * @return true if the shade was open, else false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java index a930a897c2dc..d4458e29a306 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.phone; import android.util.Log; -import android.view.View; import android.view.ViewTreeObserver; import android.view.WindowManager; @@ -182,12 +181,6 @@ public class ShadeControllerImpl implements ShadeController { } @Override - public void goToLockedShade(View startingChild) { - // TODO: Move this code out of StatusBar into ShadeController. - getStatusBar().goToLockedShade(startingChild); - } - - @Override public boolean collapsePanel() { if (!getNotificationPanelViewController().isFullyCollapsed()) { // close the shade if it was open diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index ded3be43501a..ec012bfde9b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -194,6 +194,7 @@ import com.android.systemui.statusbar.KeyboardShortcuts; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.LiftReveal; import com.android.systemui.statusbar.LightRevealScrim; +import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; @@ -347,6 +348,8 @@ public class StatusBar extends SystemUI implements DemoMode, ONLY_CORE_APPS = onlyCoreApps; } + private LockscreenShadeTransitionController mLockscreenShadeTransitionController; + public interface ExpansionChangedListener { void onExpansionChanged(float expansion, boolean expanded); } @@ -438,9 +441,6 @@ public class StatusBar extends SystemUI implements DemoMode, KeyguardIndicationController mKeyguardIndicationController; - // RemoteInputView to be activated after unlock - private View mPendingRemoteInputView; - private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; private View mReportRejectedTouch; @@ -650,12 +650,6 @@ public class StatusBar extends SystemUI implements DemoMode, private final ScreenLifecycle mScreenLifecycle; private final WakefulnessLifecycle mWakefulnessLifecycle; - private final View.OnClickListener mGoToLockedShadeListener = v -> { - if (mState == StatusBarState.KEYGUARD) { - wakeUpIfDozing(SystemClock.uptimeMillis(), v, "SHADE_CLICK"); - goToLockedShade(null); - } - }; private boolean mNoAnimationOnNextBarModeChange; private final SysuiStatusBarStateController mStatusBarStateController; @@ -798,6 +792,7 @@ public class StatusBar extends SystemUI implements DemoMode, OngoingCallController ongoingCallController, SystemStatusAnimationScheduler animationScheduler, StatusBarLocationPublisher locationPublisher, + LockscreenShadeTransitionController lockscreenShadeTransitionController, FeatureFlags featureFlags, KeyguardUnlockAnimationController keyguardUnlockAnimationController) { super(context); @@ -882,6 +877,8 @@ public class StatusBar extends SystemUI implements DemoMode, mAnimationScheduler = animationScheduler; mStatusBarLocationPublisher = locationPublisher; mFeatureFlags = featureFlags; + mLockscreenShadeTransitionController = lockscreenShadeTransitionController; + lockscreenShadeTransitionController.setStatusbar(this); mExpansionChangedListeners = new ArrayList<>(); @@ -1422,7 +1419,8 @@ public class StatusBar extends SystemUI implements DemoMode, mDozeScrimController, mScrimController, mNotificationShadeWindowController, mDynamicPrivacyController, mKeyguardStateController, mKeyguardIndicationController, - this /* statusBar */, mShadeController, mCommandQueue, mInitController, + this /* statusBar */, mShadeController, + mLockscreenShadeTransitionController, mCommandQueue, mInitController, mNotificationInterruptStateProvider); mNotificationShelfController.setOnActivatedListener(mPresenter); @@ -1502,7 +1500,6 @@ public class StatusBar extends SystemUI implements DemoMode, private void inflateShelf() { mNotificationShelfController = mSuperStatusBarViewFactory .getNotificationShelfController(mStackScroller); - mNotificationShelfController.setOnClickListener(mGoToLockedShadeListener); } @Override @@ -1675,7 +1672,7 @@ public class StatusBar extends SystemUI implements DemoMode, final boolean expandEnabled = mDeviceProvisionedController.isDeviceProvisioned() && (mUserSetup || mUserSwitcherController == null || !mUserSwitcherController.isSimpleUserSwitcher()) - && ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0) + && !isShadeDisabled() && ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0) && !mDozing && !ONLY_CORE_APPS; @@ -1683,6 +1680,10 @@ public class StatusBar extends SystemUI implements DemoMode, Log.d(TAG, "updateQsExpansionEnabled - QS Expand enabled: " + expandEnabled); } + public boolean isShadeDisabled() { + return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0; + } + public void addQsTile(ComponentName tile) { if (mQSPanelController != null && mQSPanelController.getHost() != null) { mQSPanelController.getHost().addTile(tile); @@ -3333,7 +3334,6 @@ public class StatusBar extends SystemUI implements DemoMode, public void showKeyguard() { mStatusBarStateController.setKeyguardRequested(true); mStatusBarStateController.setLeaveOpenOnKeyguardHide(false); - mPendingRemoteInputView = null; updateIsKeyguard(); mAssistManagerLazy.get().onLockscreenShown(); } @@ -3392,11 +3392,6 @@ public class StatusBar extends SystemUI implements DemoMode, mStatusBarStateController.setState(StatusBarState.KEYGUARD); } updatePanelExpansionForKeyguard(); - if (mDraggedDownEntry != null) { - mDraggedDownEntry.setUserLocked(false); - mDraggedDownEntry.notifyHeightChanged(false /* needsAnimation */); - mDraggedDownEntry = null; - } } private void updatePanelExpansionForKeyguard() { @@ -3524,11 +3519,7 @@ public class StatusBar extends SystemUI implements DemoMode, mStatusBarStateController.setLeaveOpenOnKeyguardHide(false); } long delay = mKeyguardStateController.calculateGoingToFullShadeDelay(); - mNotificationPanelViewController.animateToFullShade(delay); - if (mDraggedDownEntry != null) { - mDraggedDownEntry.setUserLocked(false); - mDraggedDownEntry = null; - } + mLockscreenShadeTransitionController.onHideKeyguard(delay); // Disable layout transitions in navbar for this transition because the load is just // too heavy for the CPU and GPU on any device. @@ -3719,6 +3710,22 @@ public class StatusBar extends SystemUI implements DemoMode, } } + /** + * Show the bouncer if we're currently on the keyguard or shade locked and aren't hiding. + * @param performAction the action to perform when the bouncer is dismissed. + * @param cancelAction the action to perform when unlock is aborted. + */ + public void showBouncerWithDimissAndCancelIfKeyguard(OnDismissAction performAction, + Runnable cancelAction) { + if ((mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) + && !mKeyguardViewMediator.isHiding()) { + mStatusBarKeyguardViewManager.dismissWithAction(performAction, cancelAction, + false /* afterKeyguardGone */); + } else if (cancelAction != null) { + cancelAction.run(); + } + } + void instantCollapseNotificationPanel() { mNotificationPanelViewController.instantCollapse(); mShadeController.runPostCollapseRunnables(); @@ -3896,47 +3903,6 @@ public class StatusBar extends SystemUI implements DemoMode, } /** - * If secure with redaction: Show bouncer, go to unlocked shade. - * - * <p>If secure without redaction or no security: Go to {@link StatusBarState#SHADE_LOCKED}.</p> - * - * @param expandView The view to expand after going to the shade. - */ - void goToLockedShade(View expandView) { - if ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) { - return; - } - - int userId = mLockscreenUserManager.getCurrentUserId(); - ExpandableNotificationRow row = null; - NotificationEntry entry = null; - if (expandView instanceof ExpandableNotificationRow) { - entry = ((ExpandableNotificationRow) expandView).getEntry(); - entry.setUserExpanded(true /* userExpanded */, true /* allowChildExpansion */); - // Indicate that the group expansion is changing at this time -- this way the group - // and children backgrounds / divider animations will look correct. - entry.setGroupExpansionChanging(true); - userId = entry.getSbn().getUserId(); - } - boolean fullShadeNeedsBouncer = !mLockscreenUserManager. - userAllowsPrivateNotificationsInPublic(mLockscreenUserManager.getCurrentUserId()) - || !mLockscreenUserManager.shouldShowLockscreenNotifications() - || mFalsingCollector.shouldEnforceBouncer(); - if (mKeyguardBypassController.getBypassEnabled()) { - fullShadeNeedsBouncer = false; - } - if (mLockscreenUserManager.isLockscreenPublicMode(userId) && fullShadeNeedsBouncer) { - mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); - showBouncerIfKeyguard(); - mDraggedDownEntry = entry; - mPendingRemoteInputView = null; - } else { - mNotificationPanelViewController.animateToFullShade(0 /* delay */); - mStatusBarStateController.setState(StatusBarState.SHADE_LOCKED); - } - } - - /** * Propagation of the bouncer state, indicating that it's fully visible. */ public void setBouncerShowing(boolean bouncerShowing) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 75c544d3aa4b..aa58527cb32e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -48,6 +48,7 @@ import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; +import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; @@ -112,6 +113,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, private final KeyguardIndicationController mKeyguardIndicationController; private final StatusBar mStatusBar; private final ShadeController mShadeController; + private final LockscreenShadeTransitionController mShadeTransitionController; private final CommandQueue mCommandQueue; private final AccessibilityManager mAccessibilityManager; @@ -138,6 +140,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, KeyguardIndicationController keyguardIndicationController, StatusBar statusBar, ShadeController shadeController, + LockscreenShadeTransitionController shadeTransitionController, CommandQueue commandQueue, InitController initController, NotificationInterruptStateProvider notificationInterruptStateProvider) { @@ -149,6 +152,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, // TODO: use KeyguardStateController#isOccluded to remove this dependency mStatusBar = statusBar; mShadeController = shadeController; + mShadeTransitionController = shadeTransitionController; mCommandQueue = commandQueue; mAboveShelfObserver = new AboveShelfObserver(stackScrollerController.getView()); mNotificationShadeWindowController = notificationShadeWindowController; @@ -394,7 +398,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mHeadsUpManager.setExpanded(clickedEntry, nowExpanded); if (nowExpanded) { if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { - mShadeController.goToLockedShade(clickedEntry.getRow()); + mShadeTransitionController.goToLockedShade(clickedEntry.getRow()); } else if (clickedEntry.isSensitive() && mDynamicPrivacyController.isInLockedDownShade()) { mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index d0d2cb288aa5..9722d6841c62 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -51,6 +51,7 @@ import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.KeyguardIndicationController; +import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -213,6 +214,7 @@ public interface StatusBarPhoneModule { OngoingCallController ongoingCallController, SystemStatusAnimationScheduler animationScheduler, StatusBarLocationPublisher locationPublisher, + LockscreenShadeTransitionController transitionController, FeatureFlags featureFlags, KeyguardUnlockAnimationController keyguardUnlockAnimationController) { return new StatusBar( @@ -299,6 +301,7 @@ public interface StatusBarPhoneModule { ongoingCallController, animationScheduler, locationPublisher, + transitionController, featureFlags, keyguardUnlockAnimationController); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt index a974421a9b7c..c6aef4a18373 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.animation.UniqueObjectHostView import org.junit.Assert.assertNotNull @@ -79,6 +80,8 @@ class MediaHierarchyManagerTest : SysuiTestCase() { private lateinit var wakefulnessLifecycle: WakefulnessLifecycle @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager + @Mock + private lateinit var configurationController: ConfigurationController @Captor private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)> @Captor @@ -98,6 +101,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { bypassController, mediaCarouselController, notificationLockscreenUserManager, + configurationController, wakefulnessLifecycle, statusBarKeyguardViewManager) verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt new file mode 100644 index 000000000000..18b6c3074d08 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -0,0 +1,225 @@ +package com.android.systemui.statusbar + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.testing.TestableLooper.RunWithLooper +import android.util.DisplayMetrics +import androidx.test.filters.SmallTest +import com.android.systemui.ExpandHelper +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.media.MediaHierarchyManager +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.qs.QS +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.notification.row.NotificationTestHelper +import com.android.systemui.statusbar.notification.stack.AmbientState +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.statusbar.phone.LockscreenGestureLogger +import com.android.systemui.statusbar.phone.NotificationPanelViewController +import com.android.systemui.statusbar.phone.ScrimController +import com.android.systemui.statusbar.phone.StatusBar +import com.android.systemui.statusbar.policy.ConfigurationController +import org.junit.After +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyFloat +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyLong +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit +import org.mockito.Mockito.`when` as whenever + +private fun <T> anyObject(): T { + return Mockito.anyObject<T>() +} + +@SmallTest +@RunWithLooper(setAsMainLooper = true) +@RunWith(AndroidTestingRunner::class) +class LockscreenShadeTransitionControllerTest : SysuiTestCase() { + + lateinit var transitionController: LockscreenShadeTransitionController + lateinit var row: ExpandableNotificationRow + @Mock lateinit var statusbarStateController: SysuiStatusBarStateController + @Mock lateinit var lockscreenGestureLogger: LockscreenGestureLogger + @Mock lateinit var keyguardBypassController: KeyguardBypassController + @Mock lateinit var lockScreenUserManager: NotificationLockscreenUserManager + @Mock lateinit var falsingCollector: FalsingCollector + @Mock lateinit var ambientState: AmbientState + @Mock lateinit var displayMetrics: DisplayMetrics + @Mock lateinit var mediaHierarchyManager: MediaHierarchyManager + @Mock lateinit var scrimController: ScrimController + @Mock lateinit var configurationController: ConfigurationController + @Mock lateinit var falsingManager: FalsingManager + @Mock lateinit var notificationPanelController: NotificationPanelViewController + @Mock lateinit var nsslController: NotificationStackScrollLayoutController + @Mock lateinit var featureFlags: FeatureFlags + @Mock lateinit var stackscroller: NotificationStackScrollLayout + @Mock lateinit var expandHelperCallback: ExpandHelper.Callback + @Mock lateinit var statusbar: StatusBar + @Mock lateinit var qS: QS + @JvmField @Rule val mockito = MockitoJUnit.rule() + + @Before + fun setup() { + val helper = NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)) + row = helper.createRow() + transitionController = LockscreenShadeTransitionController( + statusBarStateController = statusbarStateController, + lockscreenGestureLogger = lockscreenGestureLogger, + keyguardBypassController = keyguardBypassController, + lockScreenUserManager = lockScreenUserManager, + falsingCollector = falsingCollector, + ambientState = ambientState, + displayMetrics = displayMetrics, + mediaHierarchyManager = mediaHierarchyManager, + scrimController = scrimController, + featureFlags = featureFlags, + context = context, + configurationController = configurationController, + falsingManager = falsingManager + ) + whenever(nsslController.view).thenReturn(stackscroller) + whenever(nsslController.expandHelperCallback).thenReturn(expandHelperCallback) + transitionController.notificationPanelController = notificationPanelController + transitionController.statusbar = statusbar + transitionController.qS = qS + transitionController.setStackScroller(nsslController) + whenever(statusbarStateController.state).thenReturn(StatusBarState.KEYGUARD) + whenever(nsslController.isInLockedDownShade).thenReturn(false) + whenever(qS.isFullyCollapsed).thenReturn(true) + whenever(lockScreenUserManager.userAllowsPrivateNotificationsInPublic(anyInt())).thenReturn( + true) + whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(true) + whenever(lockScreenUserManager.isLockscreenPublicMode(anyInt())).thenReturn(true) + whenever(falsingCollector.shouldEnforceBouncer()).thenReturn(false) + whenever(keyguardBypassController.bypassEnabled).thenReturn(false) + clearInvocations(statusbar) + } + + @After + fun tearDown() { + transitionController.dragDownAnimator?.cancel() + } + + @Test + fun testCantDragDownWhenQSExpanded() { + assertTrue("Can't drag down on keyguard", transitionController.canDragDown()) + whenever(qS.isFullyCollapsed).thenReturn(false) + assertFalse("Can drag down when QS is expanded", transitionController.canDragDown()) + } + + @Test + fun testCanDragDownInLockedDownShade() { + whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) + assertFalse("Can drag down in shade locked", transitionController.canDragDown()) + whenever(nsslController.isInLockedDownShade).thenReturn(true) + assertTrue("Can't drag down in locked down shade", transitionController.canDragDown()) + } + + @Test + fun testGoingToLockedShade() { + transitionController.goToLockedShade(null) + verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED) + } + + @Test + fun testGoToLockedShadeOnlyOnKeyguard() { + whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) + transitionController.goToLockedShade(null) + whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE) + transitionController.goToLockedShade(null) + whenever(statusbarStateController.state).thenReturn(StatusBarState.FULLSCREEN_USER_SWITCHER) + transitionController.goToLockedShade(null) + verify(statusbarStateController, never()).setState(anyInt()) + } + + @Test + fun testDontGoWhenShadeDisabled() { + whenever(statusbar.isShadeDisabled).thenReturn(true) + transitionController.goToLockedShade(null) + verify(statusbarStateController, never()).setState(anyInt()) + } + + @Test + fun testUserExpandsViewOnGoingToFullShade() { + assertFalse("Row shouldn't be user expanded yet", row.isUserExpanded) + transitionController.goToLockedShade(row) + assertTrue("Row wasn't user expanded on drag down", row.isUserExpanded) + } + + @Test + fun testTriggeringBouncerWhenPrivateNotificationsArentAllowed() { + whenever(lockScreenUserManager.userAllowsPrivateNotificationsInPublic(anyInt())).thenReturn( + false) + transitionController.goToLockedShade(null) + verify(statusbarStateController, never()).setState(anyInt()) + verify(statusbarStateController).setLeaveOpenOnKeyguardHide(true) + verify(statusbar).showBouncerWithDimissAndCancelIfKeyguard(anyObject(), anyObject()) + } + + @Test + fun testTriggeringBouncerNoNotificationsOnLockscreen() { + whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false) + transitionController.goToLockedShade(null) + verify(statusbarStateController, never()).setState(anyInt()) + verify(statusbarStateController).setLeaveOpenOnKeyguardHide(true) + verify(statusbar).showBouncerWithDimissAndCancelIfKeyguard(anyObject(), anyObject()) + } + + @Test + fun testGoToLockedShadeCreatesQSAnimation() { + transitionController.goToLockedShade(null) + verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED) + verify(notificationPanelController).animateToFullShade(anyLong()) + assertNotNull(transitionController.dragDownAnimator) + } + + @Test + fun testGoToLockedShadeDoesntCreateQSAnimation() { + transitionController.goToLockedShade(null, needsQSAnimation = false) + verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED) + verify(notificationPanelController).animateToFullShade(anyLong()) + assertNull(transitionController.dragDownAnimator) + } + + @Test + fun testDragDownAmountDoesntCallOutInLockedDownShade() { + whenever(nsslController.isInLockedDownShade).thenReturn(true) + transitionController.dragDownAmount = 10f + verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat()) + verify(mediaHierarchyManager, never()).setTransitionToFullShadeAmount(anyFloat()) + verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat()) + verify(notificationPanelController, never()).setTransitionToFullShadeAmount(anyFloat(), + anyBoolean(), anyLong()) + verify(qS, never()).setTransitionToFullShadeAmount(anyFloat(), anyBoolean()) + } + + @Test + fun testDragDownAmountCallsOut() { + transitionController.dragDownAmount = 10f + verify(nsslController).setTransitionToFullShadeAmount(anyFloat()) + verify(mediaHierarchyManager).setTransitionToFullShadeAmount(anyFloat()) + verify(scrimController).setTransitionToFullShadeProgress(anyFloat()) + verify(notificationPanelController).setTransitionToFullShadeAmount(anyFloat(), + anyBoolean(), anyLong()) + verify(qS).setTransitionToFullShadeAmount(anyFloat(), anyBoolean()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 84fb3689b0b0..8758e1609fa8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -142,7 +142,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mNotificationSectionsManager, mGroupMembershipManger, mGroupExpansionManager, - mStatusBarStateController, mAmbientState, mFeatureFlags); mStackScrollerInternal.initView(getContext(), mKeyguardBypassEnabledProvider, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java index 895339fb7aaa..f376e88b2cb1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java @@ -50,6 +50,7 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.FeatureFlags; +import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -121,6 +122,7 @@ public class NotificationStackScrollerControllerTest extends SysuiTestCase { @Mock private NotificationEntryManager mEntryManager; @Mock private IStatusBarService mIStatusBarService; @Mock private UiEventLogger mUiEventLogger; + @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController; @Mock private ForegroundServiceDismissalFeatureController mFgFeatureController; @Mock private ForegroundServiceSectionController mFgServicesSectionController; @Mock private ForegroundServiceDungeonView mForegroundServiceDungeonView; @@ -173,6 +175,7 @@ public class NotificationStackScrollerControllerTest extends SysuiTestCase { mNotifPipeline, mNotifCollection, mEntryManager, + mLockscreenShadeTransitionController, mIStatusBarService, mUiEventLogger, mFgFeatureController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java index 45761df2cfdb..0783003e4973 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java @@ -82,6 +82,7 @@ import com.android.systemui.qs.QSDetailDisplayer; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.KeyguardAffordanceView; +import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShelfController; @@ -225,6 +226,8 @@ public class NotificationPanelViewTest extends SysuiTestCase { @Mock private NotificationShadeDepthController mNotificationShadeDepthController; @Mock + private LockscreenShadeTransitionController mLockscreenShadeTransitionController; + @Mock private AuthController mAuthController; @Mock private ScrimController mScrimController; @@ -311,7 +314,9 @@ public class NotificationPanelViewTest extends SysuiTestCase { mKeyguardBypassController, mHeadsUpManager, mock(NotificationRoundnessManager.class), mStatusBarStateController, - new FalsingManagerFake(), new FalsingCollectorFake()); + new FalsingManagerFake(), + mLockscreenShadeTransitionController, + new FalsingCollectorFake()); when(mKeyguardStatusViewComponentFactory.build(any())) .thenReturn(mKeyguardStatusViewComponent); when(mKeyguardStatusViewComponent.getKeyguardClockSwitchController()) @@ -327,7 +332,7 @@ public class NotificationPanelViewTest extends SysuiTestCase { mResources, mLayoutInflater, coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController, - new FalsingManagerFake(), new FalsingCollectorFake(), mShadeController, + new FalsingManagerFake(), new FalsingCollectorFake(), mNotificationLockscreenUserManager, mNotificationEntryManager, mKeyguardStateController, mStatusBarStateController, mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper, @@ -341,6 +346,7 @@ public class NotificationPanelViewTest extends SysuiTestCase { mKeyguardQsUserSwitchComponentFactory, mKeyguardUserSwitcherComponentFactory, mKeyguardStatusBarViewComponentFactory, + mLockscreenShadeTransitionController, mQSDetailDisplayer, mGroupManager, mNotificationAreaController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java index 0a3eec4d41ff..6c1a3c90d83d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java @@ -31,12 +31,12 @@ import com.android.systemui.R; import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingCollectorFake; -import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.dock.DockManager; import com.android.systemui.doze.DozeLog; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DragDownHelper; +import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; @@ -89,6 +89,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { @Mock private NotificationShadeWindowController mNotificationShadeWindowController; @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController; @Before public void setUp() { @@ -112,7 +113,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { mPulseExpansionHandler, mDynamicPrivacyController, mBypassController, - new FalsingManagerFake(), + mLockscreenShadeTransitionController, new FalsingCollectorFake(), mPluginManager, mTunerService, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index f98f00cd9187..a431a781dbb4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -41,6 +41,7 @@ import android.graphics.Color; import android.os.Handler; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.MathUtils; import android.view.View; import androidx.test.filters.SmallTest; @@ -1121,6 +1122,32 @@ public class ScrimControllerTest extends SysuiTestCase { assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ 0.8f, /* expansion */ 0.2f); } + @Test + public void testNotificationTransparency_followsTransitionToFullShade() { + mScrimController.transitionTo(ScrimState.SHADE_LOCKED); + mScrimController.setPanelExpansion(1.0f); + finishAnimationsImmediately(); + float shadeLockedAlpha = mNotificationsScrim.getViewAlpha(); + mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.setPanelExpansion(1.0f); + finishAnimationsImmediately(); + float keyguardAlpha = mNotificationsScrim.getViewAlpha(); + + mScrimController.setClipsQsScrim(true); + float progress = 0.5f; + mScrimController.setTransitionToFullShadeProgress(progress); + assertEquals(MathUtils.lerp(keyguardAlpha, shadeLockedAlpha, progress), + mNotificationsScrim.getViewAlpha(), 0.2); + progress = 0.0f; + mScrimController.setTransitionToFullShadeProgress(progress); + assertEquals(MathUtils.lerp(keyguardAlpha, shadeLockedAlpha, progress), + mNotificationsScrim.getViewAlpha(), 0.2); + progress = 1.0f; + mScrimController.setTransitionToFullShadeProgress(progress); + assertEquals(MathUtils.lerp(keyguardAlpha, shadeLockedAlpha, progress), + mNotificationsScrim.getViewAlpha(), 0.2); + } + private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) { mScrimController.setPanelExpansion(expansion); finishAnimationsImmediately(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index 8601de5611d5..ce45f2618f2b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -40,6 +40,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; +import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -127,7 +128,8 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mock(NotificationShadeWindowController.class), mock(DynamicPrivacyController.class), mock(KeyguardStateController.class), mock(KeyguardIndicationController.class), mStatusBar, - mock(ShadeControllerImpl.class), mCommandQueue, mInitController, + mock(ShadeControllerImpl.class), mock(LockscreenShadeTransitionController.class), + mCommandQueue, mInitController, mNotificationInterruptStateProvider); mInitController.executePostInitTasks(); ArgumentCaptor<NotificationInterruptSuppressor> suppressorCaptor = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index b3d52b80c815..5a3683e8e3f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -101,6 +101,7 @@ import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.KeyguardIndicationController; +import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; @@ -267,6 +268,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private OngoingCallController mOngoingCallController; @Mock private SystemStatusAnimationScheduler mAnimationScheduler; @Mock private StatusBarLocationPublisher mLocationPublisher; + @Mock private LockscreenShadeTransitionController mLockscreenTransitionController; @Mock private FeatureFlags mFeatureFlags; @Mock private IWallpaperManager mWallpaperManager; @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; @@ -437,6 +439,7 @@ public class StatusBarTest extends SysuiTestCase { mOngoingCallController, mAnimationScheduler, mLocationPublisher, + mLockscreenTransitionController, mFeatureFlags, mKeyguardUnlockAnimationController); when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class), any(ViewGroup.class), |