Fading away notification panel individually

Previously the notification panel was fading away all together,
which doesn't work when the notifications need to stay around.
We're now fading the whole panel away.

Bug: 134952761
Test: pick up phone and unlock, observe normal behavior
Change-Id: I48d12776dc8b4bd51c1a35c2b822caeef38eb850
This commit is contained in:
Selim Cinek
2019-07-01 19:44:18 -07:00
parent 9917209b15
commit 06c12d64fc
7 changed files with 220 additions and 14 deletions

View File

@@ -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"/>
<translate android:fromYDelta="5%p" android:toYDelta="0"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:interpolator="@interpolator/fast_out_slow_in"
android:startOffset="80"
android:duration="233" />
</set>

View File

@@ -98,6 +98,12 @@
<item type="id" name="keyguard_hun_animator_start_tag"/>
<item type="id" name="keyguard_hun_animator_end_tag"/>
<item type="id" name="view_group_fade_helper_modified_views"/>
<item type="id" name="view_group_fade_helper_animator"/>
<item type="id" name="view_group_fade_helper_previous_value_tag"/>
<item type="id" name="view_group_fade_helper_restore_tag"/>
<item type="id" name="view_group_fade_helper_hardware_layer"/>
<!-- Accessibility actions for the notification menu -->
<item type="id" name="action_snooze_undo"/>
<item type="id" name="action_snooze_shorter"/>

View File

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

View File

@@ -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<View> {
val viewsToFadeOut = mutableSetOf<View>()
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<View>?
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)
}
}
}

View File

@@ -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;
@@ -1232,7 +1233,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);
@@ -3169,6 +3171,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationPanel.onAffordanceLaunchEnded();
mNotificationPanel.animate().cancel();
mNotificationPanel.setAlpha(1f);
ViewGroupFadeHelper.reset(mNotificationPanel);
updateScrimController();
Trace.endSection();
return staying;

View File

@@ -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() {

View File

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