Scroll NotificationStack in sync with IME when inline replying
Bug: 264506336 Test: atest NotificationStackScrollLayoutTest, manual, i.e. testing inline replies in the shade when it needs to be scrolled Change-Id: I3fc98c2f976e3a70b4304851186eae01326e9199
This commit is contained in:
@@ -109,6 +109,10 @@ object Flags {
|
||||
@JvmField
|
||||
val NOTIFICATION_ANIMATE_BIG_PICTURE = unreleasedFlag(120, "notification_animate_big_picture")
|
||||
|
||||
@JvmField
|
||||
val ANIMATED_NOTIFICATION_SHADE_INSETS =
|
||||
unreleasedFlag(270682168, "animated_notification_shade_insets", teamfood = true)
|
||||
|
||||
// 200 - keyguard/lockscreen
|
||||
// ** Flag retired **
|
||||
// public static final BooleanFlag KEYGUARD_LAYOUT =
|
||||
|
||||
@@ -67,6 +67,7 @@ import android.view.ViewGroup;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowInsetsAnimation;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.animation.AnimationUtils;
|
||||
@@ -199,6 +200,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
|
||||
private final boolean mDebugRemoveAnimation;
|
||||
private final boolean mSimplifiedAppearFraction;
|
||||
private final boolean mUseRoundnessSourceTypes;
|
||||
private boolean mAnimatedInsets;
|
||||
|
||||
private int mContentHeight;
|
||||
private float mIntrinsicContentHeight;
|
||||
@@ -207,7 +209,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
|
||||
private int mTopPadding;
|
||||
private boolean mAnimateNextTopPaddingChange;
|
||||
private int mBottomPadding;
|
||||
private int mBottomInset = 0;
|
||||
@VisibleForTesting
|
||||
int mBottomInset = 0;
|
||||
private float mQsExpansionFraction;
|
||||
private final int mSplitShadeMinContentHeight;
|
||||
|
||||
@@ -388,9 +391,33 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private boolean mPulsing;
|
||||
private boolean mScrollable;
|
||||
private View mForcedScroll;
|
||||
private boolean mIsInsetAnimationRunning;
|
||||
|
||||
private final WindowInsetsAnimation.Callback mInsetsCallback =
|
||||
new WindowInsetsAnimation.Callback(
|
||||
WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
|
||||
|
||||
@Override
|
||||
public void onPrepare(WindowInsetsAnimation animation) {
|
||||
mIsInsetAnimationRunning = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WindowInsets onProgress(WindowInsets windowInsets,
|
||||
List<WindowInsetsAnimation> list) {
|
||||
updateBottomInset(windowInsets);
|
||||
return windowInsets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnd(WindowInsetsAnimation animation) {
|
||||
mIsInsetAnimationRunning = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @see #setHideAmount(float, float)
|
||||
@@ -584,6 +611,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
|
||||
mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
|
||||
mSimplifiedAppearFraction = featureFlags.isEnabled(Flags.SIMPLIFIED_APPEAR_FRACTION);
|
||||
mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
|
||||
setAnimatedInsetsEnabled(featureFlags.isEnabled(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS));
|
||||
mSectionsManager = Dependency.get(NotificationSectionsManager.class);
|
||||
mScreenOffAnimationController =
|
||||
Dependency.get(ScreenOffAnimationController.class);
|
||||
@@ -622,6 +650,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
|
||||
mGroupMembershipManager = Dependency.get(GroupMembershipManager.class);
|
||||
mGroupExpansionManager = Dependency.get(GroupExpansionManager.class);
|
||||
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
|
||||
if (mAnimatedInsets) {
|
||||
setWindowInsetsAnimationCallback(mInsetsCallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -689,6 +720,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
|
||||
mHasFilteredOutSeenNotifications = hasFilteredOutSeenNotifications;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setAnimatedInsetsEnabled(boolean enabled) {
|
||||
mAnimatedInsets = enabled;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
|
||||
public void updateFooter() {
|
||||
@@ -1781,7 +1817,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
|
||||
return;
|
||||
}
|
||||
mForcedScroll = v;
|
||||
scrollTo(v);
|
||||
if (mAnimatedInsets) {
|
||||
updateForcedScroll();
|
||||
} else {
|
||||
scrollTo(v);
|
||||
}
|
||||
}
|
||||
|
||||
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
|
||||
@@ -1813,26 +1853,46 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
|
||||
+ ((!isExpanded() && isPinnedHeadsUp(v)) ? mHeadsUpInset : getTopPadding());
|
||||
}
|
||||
|
||||
private void updateBottomInset(WindowInsets windowInsets) {
|
||||
mBottomInset = windowInsets.getInsets(WindowInsets.Type.ime()).bottom;
|
||||
|
||||
if (mForcedScroll != null) {
|
||||
updateForcedScroll();
|
||||
}
|
||||
|
||||
int range = getScrollRange();
|
||||
if (mOwnScrollY > range) {
|
||||
setOwnScrollY(range);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
|
||||
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
|
||||
mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom;
|
||||
if (!mAnimatedInsets) {
|
||||
mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom;
|
||||
}
|
||||
mWaterfallTopInset = 0;
|
||||
final DisplayCutout cutout = insets.getDisplayCutout();
|
||||
if (cutout != null) {
|
||||
mWaterfallTopInset = cutout.getWaterfallInsets().top;
|
||||
}
|
||||
|
||||
int range = getScrollRange();
|
||||
if (mOwnScrollY > range) {
|
||||
// HACK: We're repeatedly getting staggered insets here while the IME is
|
||||
// animating away. To work around that we'll wait until things have settled.
|
||||
removeCallbacks(mReclamp);
|
||||
postDelayed(mReclamp, 50);
|
||||
} else if (mForcedScroll != null) {
|
||||
// The scroll was requested before we got the actual inset - in case we need
|
||||
// to scroll up some more do so now.
|
||||
scrollTo(mForcedScroll);
|
||||
if (mAnimatedInsets && !mIsInsetAnimationRunning) {
|
||||
// update bottom inset e.g. after rotation
|
||||
updateBottomInset(insets);
|
||||
}
|
||||
if (!mAnimatedInsets) {
|
||||
int range = getScrollRange();
|
||||
if (mOwnScrollY > range) {
|
||||
// HACK: We're repeatedly getting staggered insets here while the IME is
|
||||
// animating away. To work around that we'll wait until things have settled.
|
||||
removeCallbacks(mReclamp);
|
||||
postDelayed(mReclamp, 50);
|
||||
} else if (mForcedScroll != null) {
|
||||
// The scroll was requested before we got the actual inset - in case we need
|
||||
// to scroll up some more do so now.
|
||||
scrollTo(mForcedScroll);
|
||||
}
|
||||
}
|
||||
return insets;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.systemui.statusbar.notification.stack;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.WindowInsets.Type.ime;
|
||||
|
||||
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
|
||||
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
|
||||
@@ -46,6 +47,7 @@ import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.graphics.Insets;
|
||||
import android.graphics.Rect;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
@@ -54,6 +56,8 @@ import android.util.MathUtils;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowInsetsAnimation;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.test.annotation.UiThreadTest;
|
||||
@@ -91,6 +95,8 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Tests for {@link NotificationStackScrollLayout}.
|
||||
*/
|
||||
@@ -843,6 +849,19 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
|
||||
verify(mEmptyShadeView).setFooterText(not(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWindowInsetAnimationProgress_updatesBottomInset() {
|
||||
int bottomImeInset = 100;
|
||||
mStackScrollerInternal.setAnimatedInsetsEnabled(true);
|
||||
WindowInsets windowInsets = new WindowInsets.Builder()
|
||||
.setInsets(ime(), Insets.of(0, 0, 0, bottomImeInset)).build();
|
||||
ArrayList<WindowInsetsAnimation> windowInsetsAnimations = new ArrayList<>();
|
||||
mStackScrollerInternal
|
||||
.dispatchWindowInsetsAnimationProgress(windowInsets, windowInsetsAnimations);
|
||||
|
||||
assertEquals(bottomImeInset, mStackScrollerInternal.mBottomInset);
|
||||
}
|
||||
|
||||
private void setBarStateForTest(int state) {
|
||||
// Can't inject this through the listener or we end up on the actual implementation
|
||||
// rather than the mock because the spy just coppied the anonymous inner /shruggie.
|
||||
|
||||
Reference in New Issue
Block a user