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;
}
}