diff --git a/core/res/res/anim/lock_screen_behind_enter_subtle.xml b/core/res/res/anim/lock_screen_behind_enter_subtle.xml index 23b26b791a57..f9f69b12514c 100644 --- a/core/res/res/anim/lock_screen_behind_enter_subtle.xml +++ b/core/res/res/anim/lock_screen_behind_enter_subtle.xml @@ -23,9 +23,11 @@ android:fromAlpha="0.0" android:toAlpha="1.0" android:fillEnabled="true" android:fillBefore="true" android:interpolator="@interpolator/linear" + android:startOffset="80" android:duration="233"/> \ No newline at end of file diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 09ac37c54c85..372718139886 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -98,6 +98,12 @@ + + + + + + diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index d7ca4d006082..4dcb9f94088c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -61,6 +61,7 @@ import android.util.EventLog; import android.util.Log; import android.util.Slog; import android.util.SparseArray; +import android.view.View; import android.view.ViewGroup; import android.view.WindowManagerPolicyConstants; import android.view.animation.Animation; @@ -85,6 +86,7 @@ import com.android.systemui.SystemUIFactory; import com.android.systemui.UiOffloadThread; import com.android.systemui.classifier.FalsingManagerFactory; import com.android.systemui.statusbar.phone.BiometricUnlockController; +import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationPanelView; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; @@ -2060,9 +2062,11 @@ public class KeyguardViewMediator extends SystemUI { public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar, ViewGroup container, NotificationPanelView panelView, - BiometricUnlockController biometricUnlockController, ViewGroup lockIconContainer) { + BiometricUnlockController biometricUnlockController, ViewGroup lockIconContainer, + View notificationContainer, KeyguardBypassController bypassController) { mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container, panelView, - biometricUnlockController, mDismissCallbackRegistry, lockIconContainer); + biometricUnlockController, mDismissCallbackRegistry, lockIconContainer, + notificationContainer, bypassController); return mStatusBarKeyguardViewManager; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt new file mode 100644 index 000000000000..847d1ccddddf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.notification + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +import android.view.View +import android.view.ViewGroup +import com.android.systemui.Interpolators +import com.android.systemui.R + +/** + * Class to help with fading of view groups without fading one subview + */ +class ViewGroupFadeHelper { + companion object { + private val visibilityIncluder = { + view: View -> view.visibility == View.VISIBLE + } + + /** + * Fade out all views of a root except a single child. This will iterate over all children + * of the view and make sure that the animation works smoothly. + * @param root the view root to fade the children away + * @param excludedView which view should remain + * @param duration the duration of the animation + */ + @JvmStatic + fun fadeOutAllChildrenExcept(root: ViewGroup, excludedView: View, duration: Long, + endRunnable: Runnable?) { + // starting from the view going up, we are adding the siblings of the child to the set + // of views that need to be faded. + val viewsToFadeOut = gatherViews(root, excludedView, visibilityIncluder) + + // Applying the right layertypes for the animation + for (viewToFade in viewsToFadeOut) { + if (viewToFade.hasOverlappingRendering + && viewToFade.layerType == View.LAYER_TYPE_NONE) { + viewToFade.setLayerType(View.LAYER_TYPE_HARDWARE, null) + viewToFade.setTag(R.id.view_group_fade_helper_hardware_layer, true) + } + } + + val animator = ValueAnimator.ofFloat(1.0f, 0.0f).apply { + this.duration = duration + interpolator = Interpolators.ALPHA_OUT + addUpdateListener { animation -> + val previousSetAlpha = root.getTag( + R.id.view_group_fade_helper_previous_value_tag) as Float? + val newAlpha = animation.animatedValue as Float + for (viewToFade in viewsToFadeOut) { + if (viewToFade.alpha != previousSetAlpha) { + // A value was set that wasn't set from our view, let's store it and restore + // it at the end + viewToFade.setTag(R.id.view_group_fade_helper_restore_tag, viewToFade.alpha) + } + viewToFade.alpha = newAlpha + } + root.setTag(R.id.view_group_fade_helper_previous_value_tag, newAlpha) + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + endRunnable?.run() + } + }) + start() + } + root.setTag(R.id.view_group_fade_helper_modified_views, viewsToFadeOut) + root.setTag(R.id.view_group_fade_helper_animator, animator) + } + + private fun gatherViews(root: ViewGroup, excludedView: View, + shouldInclude: (View) -> Boolean): MutableSet { + val viewsToFadeOut = mutableSetOf() + var parent = excludedView.parent as ViewGroup? + var viewContainingExcludedView = excludedView; + while (parent != null) { + for (i in 0 until parent.childCount) { + val child = parent.getChildAt(i) + if (shouldInclude.invoke(child) && viewContainingExcludedView != child) { + viewsToFadeOut.add(child) + } + } + if (parent == root) { + break; + } + viewContainingExcludedView = parent + parent = parent.parent as ViewGroup? + } + return viewsToFadeOut + } + + /** + * Reset all view alphas for views previously transformed away. + */ + @JvmStatic + fun reset(root: ViewGroup) { + @Suppress("UNCHECKED_CAST") + val modifiedViews = root.getTag(R.id.view_group_fade_helper_modified_views) + as MutableSet? + val animator = root.getTag(R.id.view_group_fade_helper_animator) as Animator? + if (modifiedViews == null || animator == null) { + // nothing to restore + return + } + animator.cancel() + val lastSetValue = root.getTag( + R.id.view_group_fade_helper_previous_value_tag) as Float? + for (viewToFade in modifiedViews) { + val restoreAlpha = viewToFade.getTag( + R.id.view_group_fade_helper_restore_tag) as Float? + if (restoreAlpha == null) { + continue + } + if (lastSetValue == viewToFade.alpha) { + // it was modified after the transition! + viewToFade.alpha = restoreAlpha + } + val needsLayerReset = viewToFade.getTag( + R.id.view_group_fade_helper_hardware_layer) as Boolean? + if (needsLayerReset == true) { + viewToFade.setLayerType(View.LAYER_TYPE_NONE, null) + viewToFade.setTag(R.id.view_group_fade_helper_hardware_layer, null) + } + viewToFade.setTag(R.id.view_group_fade_helper_restore_tag, null) + } + root.setTag(R.id.view_group_fade_helper_modified_views, null) + root.setTag(R.id.view_group_fade_helper_previous_value_tag, null) + root.setTag(R.id.view_group_fade_helper_animator, null) + } + } +} 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 dfcc1b2f04d5..f9d3314c4abd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -202,6 +202,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationListController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; +import com.android.systemui.statusbar.notification.ViewGroupFadeHelper; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; @@ -1235,7 +1236,8 @@ public class StatusBar extends SystemUI implements DemoMode, new Handler(), mKeyguardUpdateMonitor, mKeyguardBypassController); mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this, getBouncerContainer(), mNotificationPanel, mBiometricUnlockController, - mStatusBarWindow.findViewById(R.id.lock_icon_container)); + mStatusBarWindow.findViewById(R.id.lock_icon_container), mStackScroller, + mKeyguardBypassController); mKeyguardIndicationController .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); @@ -3172,6 +3174,7 @@ public class StatusBar extends SystemUI implements DemoMode, mNotificationPanel.onAffordanceLaunchEnded(); mNotificationPanel.animate().cancel(); mNotificationPanel.setAlpha(1f); + ViewGroupFadeHelper.reset(mNotificationPanel); updateScrimController(); Trace.endSection(); return staying; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 20e8e773e91c..20bc2a742de6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.plugins.ActivityStarter.OnDismissAction; +import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_FADING; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING; @@ -51,6 +52,7 @@ import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.notification.ViewGroupFadeHelper; import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardMonitor; @@ -82,6 +84,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb // make everything a bit slower to bridge a gap until the user is unlocked and home screen has // dranw its first frame. private static final long KEYGUARD_DISMISS_DURATION_LOCKED = 2000; + private static final long BYPASS_PANEL_FADE_DURATION = 67; private static String TAG = "StatusBarKeyguardViewManager"; @@ -132,6 +135,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private ViewGroup mContainer; private ViewGroup mLockIconContainer; + private View mNotificationContainer; protected KeyguardBouncer mBouncer; protected boolean mShowing; @@ -165,9 +169,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb (KeyguardMonitorImpl) Dependency.get(KeyguardMonitor.class); private final NotificationMediaManager mMediaManager = Dependency.get(NotificationMediaManager.class); - private final StatusBarStateController mStatusBarStateController = - Dependency.get(StatusBarStateController.class); + private final SysuiStatusBarStateController mStatusBarStateController = + (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class); private final DockManager mDockManager; + private KeyguardBypassController mBypassController; private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { @@ -205,7 +210,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb NotificationPanelView notificationPanelView, BiometricUnlockController biometricUnlockController, DismissCallbackRegistry dismissCallbackRegistry, - ViewGroup lockIconContainer) { + ViewGroup lockIconContainer, View notificationContainer, + KeyguardBypassController bypassController) { mStatusBar = statusBar; mContainer = container; mLockIconContainer = lockIconContainer; @@ -218,6 +224,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mExpansionCallback); mNotificationPanelView = notificationPanelView; notificationPanelView.setExpansionListener(this); + mBypassController = bypassController; + mNotificationContainer = notificationContainer; } @Override @@ -555,12 +563,32 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mBiometricUnlockController.startKeyguardFadingAway(); hideBouncer(true /* destroyView */); if (wakeUnlockPulsing) { - mStatusBar.fadeKeyguardWhilePulsing(); + if (needsBypassFading()) { + ViewGroupFadeHelper.fadeOutAllChildrenExcept(mNotificationPanelView, + mNotificationContainer, + BYPASS_PANEL_FADE_DURATION, + () -> { + mStatusBar.hideKeyguard(); + onKeyguardFadedAway(); + }); + } else { + mStatusBar.fadeKeyguardWhilePulsing(); + } wakeAndUnlockDejank(); } else { - boolean staying = mStatusBar.hideKeyguard(); + boolean staying = mStatusBarStateController.leaveOpenOnKeyguardHide(); if (!staying) { mStatusBarWindowController.setKeyguardFadingAway(true); + if (needsBypassFading()) { + ViewGroupFadeHelper.fadeOutAllChildrenExcept(mNotificationPanelView, + mNotificationContainer, + BYPASS_PANEL_FADE_DURATION, + () -> { + mStatusBar.hideKeyguard(); + }); + } else { + mStatusBar.hideKeyguard(); + } // hide() will happen asynchronously and might arrive after the scrims // were already hidden, this means that the transition callback won't // be triggered anymore and StatusBarWindowController will be forever in @@ -568,6 +596,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mStatusBar.updateScrimController(); wakeAndUnlockDejank(); } else { + mStatusBar.hideKeyguard(); mStatusBar.finishKeyguardFadingAway(); mBiometricUnlockController.finishKeyguardFadingAway(); } @@ -580,6 +609,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb StatsLog.KEYGUARD_STATE_CHANGED__STATE__HIDDEN); } + private boolean needsBypassFading() { + return (mBiometricUnlockController.getMode() == MODE_UNLOCK_FADING + || mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK_PULSING + || mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK) + && mBypassController.getBypassEnabled(); + } + @Override public void onDensityOrFontScaleChanged() { hideBouncer(true /* destroyView */); @@ -602,6 +638,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void onKeyguardFadedAway() { mContainer.postDelayed(() -> mStatusBarWindowController.setKeyguardFadingAway(false), 100); + ViewGroupFadeHelper.reset(mNotificationPanelView); mStatusBar.finishKeyguardFadingAway(); mBiometricUnlockController.finishKeyguardFadingAway(); WindowManagerGlobal.getInstance().trimMemory( @@ -821,8 +858,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * @return Whether subtle animation should be used for unlocking the device. */ public boolean shouldSubtleWindowAnimationsForUnlock() { - return mStatusBar.mKeyguardBypassController.getBypassEnabled() - && mStatusBar.mState == StatusBarState.KEYGUARD && !mBouncer.isAnimatingAway(); + return needsBypassFading(); } public boolean isGoingToNotificationShade() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index f50cf5a70cdc..1b33aefd1634 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.View; import android.view.ViewGroup; import androidx.test.filters.SmallTest; @@ -39,6 +40,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.SysuiStatusBarStateController; import org.junit.Before; import org.junit.Test; @@ -70,7 +72,11 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Mock private ViewGroup mLockIconContainer; @Mock - private StatusBarStateController mStatusBarStateController; + private SysuiStatusBarStateController mStatusBarStateController; + @Mock + private View mNotificationContainer; + @Mock + private KeyguardBypassController mBypassController; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @Before @@ -83,7 +89,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mViewMediatorCallback, mLockPatternUtils); mStatusBarKeyguardViewManager.registerStatusBar(mStatusBar, mContainer, mNotificationPanelView, mBiometrucUnlockController, mDismissCallbackRegistry, - mLockIconContainer); + mLockIconContainer, mNotificationContainer, mBypassController); mStatusBarKeyguardViewManager.show(null); } @@ -221,9 +227,11 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { NotificationPanelView notificationPanelView, BiometricUnlockController fingerprintUnlockController, DismissCallbackRegistry dismissCallbackRegistry, - ViewGroup lockIconContainer) { + ViewGroup lockIconContainer, View notificationContainer, + KeyguardBypassController bypassController) { super.registerStatusBar(statusBar, container, notificationPanelView, - fingerprintUnlockController, dismissCallbackRegistry, lockIconContainer); + fingerprintUnlockController, dismissCallbackRegistry, lockIconContainer, + notificationContainer, bypassController); mBouncer = StatusBarKeyguardViewManagerTest.this.mBouncer; } }