From 976de38e379842c54cc1844f4fd8fde3067ebfa0 Mon Sep 17 00:00:00 2001 From: Lucas Dupin Date: Tue, 13 Apr 2021 20:59:09 -0700 Subject: A new shade pull down animation Test: manual Test: atest ScrimViewTest Test: atest ScrimControllerTest Bug: 185683835 Fixes: 184891403 Change-Id: I1c51f3dc0d8afe97457aea8712c4322ebbda6ae4 --- .../colorextraction/drawable/ScrimDrawable.java | 198 ------------ .../src/com/android/systemui/plugins/qs/QS.java | 12 +- .../res/layout/super_notification_shade.xml | 6 +- .../keyguard/KeyguardStatusViewController.java | 15 + .../globalactions/GlobalActionsDialogLite.java | 2 +- .../systemui/globalactions/GlobalActionsImpl.java | 2 +- .../systemui/qs/NonInterceptingScrollView.java | 19 ++ .../src/com/android/systemui/qs/QSAnimator.java | 12 + .../com/android/systemui/qs/QSContainerImpl.java | 60 ++++ .../src/com/android/systemui/qs/QSFragment.java | 23 +- .../com/android/systemui/scrim/ScrimDrawable.java | 225 ++++++++++++++ .../src/com/android/systemui/scrim/ScrimView.java | 343 +++++++++++++++++++++ .../com/android/systemui/statusbar/ScrimView.java | 308 ------------------ .../NotificationStackScrollLayoutController.java | 2 +- .../phone/NotificationPanelViewController.java | 55 +++- .../statusbar/phone/PanelViewController.java | 4 +- .../systemui/statusbar/phone/ScrimController.java | 140 ++++++--- .../systemui/statusbar/phone/ScrimState.java | 43 ++- .../systemui/statusbar/phone/StatusBar.java | 3 +- .../android/systemui/wmshell/BubblesManager.java | 2 +- .../com/android/systemui/scrim/ScrimViewTest.java | 105 +++++++ .../android/systemui/statusbar/ScrimViewTest.java | 106 ------- .../statusbar/phone/ScrimControllerTest.java | 121 +++++++- 23 files changed, 1108 insertions(+), 698 deletions(-) delete mode 100644 core/java/com/android/internal/colorextraction/drawable/ScrimDrawable.java create mode 100644 packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java create mode 100644 packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java delete mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/scrim/ScrimViewTest.java delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java diff --git a/core/java/com/android/internal/colorextraction/drawable/ScrimDrawable.java b/core/java/com/android/internal/colorextraction/drawable/ScrimDrawable.java deleted file mode 100644 index 1fc126eb161d..000000000000 --- a/core/java/com/android/internal/colorextraction/drawable/ScrimDrawable.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (C) 2017 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.internal.colorextraction.drawable; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.Xfermode; -import android.graphics.drawable.Drawable; -import android.view.animation.DecelerateInterpolator; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.graphics.ColorUtils; - -/** - * Drawable used on SysUI scrims. - */ -public class ScrimDrawable extends Drawable { - private static final String TAG = "ScrimDrawable"; - private static final long COLOR_ANIMATION_DURATION = 2000; - - private final Paint mPaint; - private int mAlpha = 255; - private int mMainColor; - private ValueAnimator mColorAnimation; - private int mMainColorTo; - private float mCornerRadius; - private Rect mBounds; - private ConcaveInfo mConcaveInfo; - - public ScrimDrawable() { - mPaint = new Paint(); - mPaint.setStyle(Paint.Style.FILL); - } - - /** - * Sets the background color. - * @param mainColor the color. - * @param animated if transition should be interpolated. - */ - public void setColor(int mainColor, boolean animated) { - if (mainColor == mMainColorTo) { - return; - } - - if (mColorAnimation != null && mColorAnimation.isRunning()) { - mColorAnimation.cancel(); - } - - mMainColorTo = mainColor; - - if (animated) { - final int mainFrom = mMainColor; - - ValueAnimator anim = ValueAnimator.ofFloat(0, 1); - anim.setDuration(COLOR_ANIMATION_DURATION); - anim.addUpdateListener(animation -> { - float ratio = (float) animation.getAnimatedValue(); - mMainColor = ColorUtils.blendARGB(mainFrom, mainColor, ratio); - invalidateSelf(); - }); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation, boolean isReverse) { - if (mColorAnimation == animation) { - mColorAnimation = null; - } - } - }); - anim.setInterpolator(new DecelerateInterpolator()); - anim.start(); - mColorAnimation = anim; - } else { - mMainColor = mainColor; - invalidateSelf(); - } - } - - @Override - public void setAlpha(int alpha) { - if (alpha != mAlpha) { - mAlpha = alpha; - invalidateSelf(); - } - } - - @Override - public int getAlpha() { - return mAlpha; - } - - @Override - public void setXfermode(@Nullable Xfermode mode) { - mPaint.setXfermode(mode); - invalidateSelf(); - } - - @Override - public void setColorFilter(ColorFilter colorFilter) { - mPaint.setColorFilter(colorFilter); - } - - @Override - public ColorFilter getColorFilter() { - return mPaint.getColorFilter(); - } - - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } - - /** - * Enable drawable shape to have rounded corners with provided radius - */ - public void setRoundedCorners(float radius) { - mCornerRadius = radius; - } - - /** - * Make bottom edge concave with provided corner radius - */ - public void setBottomEdgeConcave(float radius) { - // only rounding top corners for clip out path - float[] cornerRadii = new float[]{radius, radius, radius, radius, 0, 0, 0, 0}; - mConcaveInfo = new ConcaveInfo(radius, cornerRadii); - } - - @Override - public void draw(@NonNull Canvas canvas) { - mPaint.setColor(mMainColor); - mPaint.setAlpha(mAlpha); - if (mConcaveInfo != null) { - drawConcave(canvas); - } - canvas.drawRoundRect(getBounds().left, getBounds().top, getBounds().right, - getBounds().bottom + mCornerRadius, - /* x radius*/ mCornerRadius, /* y radius*/ mCornerRadius, mPaint); - } - - private void drawConcave(Canvas canvas) { - // checking if width of clip out path needs to change - if (mBounds == null - || getBounds().right != mBounds.right - || getBounds().left != mBounds.left) { - mConcaveInfo.mPath.reset(); - float left = getBounds().left; - float right = getBounds().right; - float top = 0f; - float bottom = mConcaveInfo.mPathOverlap; - mConcaveInfo.mPath.addRoundRect(left, top, right, bottom, - mConcaveInfo.mCornerRadii, Path.Direction.CW); - } - mBounds = getBounds(); - int translation = (int) (mBounds.bottom - mConcaveInfo.mPathOverlap); - canvas.translate(0, translation); - canvas.clipOutPath(mConcaveInfo.mPath); - canvas.translate(0, -translation); - } - - @VisibleForTesting - public int getMainColor() { - return mMainColor; - } - - private static class ConcaveInfo { - private final float mPathOverlap; - private final float[] mCornerRadii; - private final Path mPath = new Path(); - - ConcaveInfo(float pathOverlap, float[] cornerRadii) { - mPathOverlap = pathOverlap; - mCornerRadii = cornerRadii; - } - } -} 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 fffcafbf88fb..4d4c909d2894 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 @@ -14,7 +14,6 @@ package com.android.systemui.plugins.qs; -import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; @@ -57,7 +56,6 @@ public interface QS extends FragmentBase { void setQsExpansion(float qsExpansionFraction, float headerTranslation); void setHeaderListening(boolean listening); void notifyCustomizeChanged(); - void setContainer(ViewGroup container); void setExpandClickListener(OnClickListener onClickListener); @@ -75,6 +73,16 @@ public interface QS extends FragmentBase { return isShowingDetail(); } + /** + * If QS should translate as we pull it down, or if it should be static. + */ + void setTranslateWhileExpanding(boolean shouldTranslate); + + /** + * A rounded corner clipping that makes QS feel as if it were behind everything. + */ + void setFancyClipping(int top, int bottom, int cornerRadius, boolean visible); + @ProvidesInterface(version = HeightListener.VERSION) interface HeightListener { int VERSION = 1; diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index 12c864cfcad3..bea50e87a29a 100644 --- a/packages/SystemUI/res/layout/super_notification_shade.xml +++ b/packages/SystemUI/res/layout/super_notification_shade.xml @@ -43,7 +43,7 @@ android:visibility="invisible" /> - - - 0) { @@ -94,4 +105,12 @@ public class NonInterceptingScrollView extends ScrollView { } return scrollRange; } + + /** + * Enable scrolling for this view. Needed because the view might be clipped but still intercepts + * touches on the lockscreen. + */ + public void setScrollingEnabled(boolean enabled) { + mScrollEnabled = enabled; + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index cefcd4a5194c..294d76590fed 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -54,6 +54,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private static final String MOVE_FULL_ROWS = "sysui_qs_move_whole_rows"; public static final float EXPANDED_TILE_DELAY = .86f; + public static final float SHORT_PARALLAX_AMOUNT = 0.1f; private static final long QQS_FADE_IN_DURATION = 200L; // Fade out faster than fade in to finish before QQS hides. private static final long QQS_FADE_OUT_DURATION = 50L; @@ -101,6 +102,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private final Executor mExecutor; private final TunerService mTunerService; private boolean mShowCollapsedOnKeyguard; + private boolean mTranslateWhileExpanding; @Inject public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader, @@ -242,6 +244,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha int width = mQs.getView() != null ? mQs.getView().getMeasuredWidth() : 0; int heightDiff = height - mQs.getHeader().getBottom() + mQs.getHeader().getPaddingBottom(); + if (!mTranslateWhileExpanding) { + heightDiff *= SHORT_PARALLAX_AMOUNT; + } firstPageBuilder.addFloat(tileLayout, "translationY", heightDiff, 0); int qqsTileHeight = 0; @@ -570,6 +575,13 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha setCurrentPosition(); }; + /** + * True whe QS will be pulled from the top, false when it will be clipped. + */ + public void setTranslateWhileExpanding(boolean shouldTranslate) { + mTranslateWhileExpanding = shouldTranslate; + } + static class HeightExpansionAnimator { private final List mViews = new ArrayList<>(); private final ValueAnimator mAnimator; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 6b09e2eb7b8b..e89803d16779 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -20,6 +20,8 @@ import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Canvas; +import android.graphics.Path; import android.graphics.Point; import android.util.AttributeSet; import android.view.View; @@ -54,6 +56,10 @@ public class QSContainerImpl extends FrameLayout { private static final PhysicsAnimator.SpringConfig BACKGROUND_SPRING = new PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY); + private int mFancyClippingTop; + private int mFancyClippingBottom; + private final float[] mFancyClippingRadii = new float[] {0, 0, 0, 0, 0, 0, 0, 0}; + private final Path mFancyClippingPath = new Path(); private int mBackgroundBottom = -1; private int mHeightOverride = -1; private View mQSDetail; @@ -70,6 +76,7 @@ public class QSContainerImpl extends FrameLayout { private int mContentPadding = -1; private boolean mAnimateBottomOnNextLayout; private int mNavBarInset = 0; + private boolean mClippingEnabled; public QSContainerImpl(Context context, AttributeSet attrs) { super(context, attrs); @@ -169,6 +176,15 @@ public class QSContainerImpl extends FrameLayout { MeasureSpec.makeMeasureSpec(getDisplayHeight(), MeasureSpec.EXACTLY)); } + @Override + public void dispatchDraw(Canvas canvas) { + if (!mFancyClippingPath.isEmpty()) { + canvas.translate(0, -getTranslationY()); + canvas.clipOutPath(mFancyClippingPath); + canvas.translate(0, getTranslationY()); + } + super.dispatchDraw(canvas); + } @Override protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, @@ -187,6 +203,7 @@ public class QSContainerImpl extends FrameLayout { super.onLayout(changed, left, top, right, bottom); updateExpansion(mAnimateBottomOnNextLayout /* animate */); mAnimateBottomOnNextLayout = false; + updateClippingPath(); } public void disable(int state1, int state2, boolean animate) { @@ -281,6 +298,7 @@ public class QSContainerImpl extends FrameLayout { public void setExpansion(float expansion) { mQsExpansion = expansion; + mQSPanelContainer.setScrollingEnabled(expansion > 0.0f); updateExpansion(); } @@ -318,4 +336,46 @@ public class QSContainerImpl extends FrameLayout { } return mSizePoint.y; } + + /** + * Clip QS bottom using a concave shape. + */ + public void setFancyClipping(int top, int bottom, int radius, boolean enabled) { + boolean updatePath = false; + if (mFancyClippingRadii[0] != radius) { + mFancyClippingRadii[0] = radius; + mFancyClippingRadii[1] = radius; + mFancyClippingRadii[2] = radius; + mFancyClippingRadii[3] = radius; + updatePath = true; + } + if (mFancyClippingTop != top) { + mFancyClippingTop = top; + updatePath = true; + } + if (mFancyClippingBottom != bottom) { + mFancyClippingBottom = bottom; + updatePath = true; + } + if (mClippingEnabled != enabled) { + mClippingEnabled = enabled; + updatePath = true; + } + + if (updatePath) { + updateClippingPath(); + } + } + + private void updateClippingPath() { + mFancyClippingPath.reset(); + if (!mClippingEnabled) { + invalidate(); + return; + } + + mFancyClippingPath.addRoundRect(0, mFancyClippingTop, getWidth(), + mFancyClippingBottom, mFancyClippingRadii, Path.Direction.CW); + invalidate(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index b95194adb9cc..d5cb777416a7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -107,6 +107,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private QuickQSPanelController mQuickQSPanelController; private QSCustomizerController mQSCustomizerController; private FeatureFlags mFeatureFlags; + /** + * When true, QS will translate from outside the screen. It will be clipped with parallax + * otherwise. + */ + private boolean mTranslateWhileExpanding; @Inject public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, @@ -254,6 +259,13 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } } + @Override + public void setFancyClipping(int top, int bottom, int cornerRadius, boolean visible) { + if (getView() instanceof QSContainerImpl) { + ((QSContainerImpl) getView()).setFancyClipping(top, bottom, cornerRadius, visible); + } + } + private void setEditLocation(View view) { View edit = view.findViewById(android.R.id.edit); int[] loc = edit.getLocationOnScreen(); @@ -393,17 +405,24 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mFooter.setListening(listening); } + @Override + public void setTranslateWhileExpanding(boolean shouldTranslate) { + mTranslateWhileExpanding = shouldTranslate; + mQSAnimator.setTranslateWhileExpanding(shouldTranslate); + } + @Override public void setQsExpansion(float expansion, float headerTranslation) { if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + headerTranslation); if (mQSAnimator != null) { final boolean showQSOnLockscreen = expansion > 0; - final boolean showQSUnlocked = headerTranslation == 0; + final boolean showQSUnlocked = headerTranslation == 0 || !mTranslateWhileExpanding; mQSAnimator.startAlphaAnimation(showQSOnLockscreen || showQSUnlocked); } mContainer.setExpansion(expansion); - final float translationScaleY = expansion - 1; + final float translationScaleY = (mTranslateWhileExpanding + ? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1); boolean onKeyguardAndExpanded = isKeyguardShowing() && !mShowCollapsedOnKeyguard; if (!mHeaderAnimating && !headerWillBeAnimating()) { getView().setTranslationY( diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java new file mode 100644 index 000000000000..0a55fbe8bf75 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2021 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.scrim; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.Xfermode; +import android.graphics.drawable.Drawable; +import android.view.animation.DecelerateInterpolator; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.graphics.ColorUtils; + +/** + * Drawable used on SysUI scrims. + */ +public class ScrimDrawable extends Drawable { + private static final String TAG = "ScrimDrawable"; + private static final long COLOR_ANIMATION_DURATION = 2000; + + private final Paint mPaint; + private int mAlpha = 255; + private int mMainColor; + private ValueAnimator mColorAnimation; + private int mMainColorTo; + private float mCornerRadius; + private Rect mBounds; + private ConcaveInfo mConcaveInfo; + private int mBottomEdgePosition; + + public ScrimDrawable() { + mPaint = new Paint(); + mPaint.setStyle(Paint.Style.FILL); + } + + /** + * Sets the background color. + * @param mainColor the color. + * @param animated if transition should be interpolated. + */ + public void setColor(int mainColor, boolean animated) { + if (mainColor == mMainColorTo) { + return; + } + + if (mColorAnimation != null && mColorAnimation.isRunning()) { + mColorAnimation.cancel(); + } + + mMainColorTo = mainColor; + + if (animated) { + final int mainFrom = mMainColor; + + ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + anim.setDuration(COLOR_ANIMATION_DURATION); + anim.addUpdateListener(animation -> { + float ratio = (float) animation.getAnimatedValue(); + mMainColor = ColorUtils.blendARGB(mainFrom, mainColor, ratio); + invalidateSelf(); + }); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation, boolean isReverse) { + if (mColorAnimation == animation) { + mColorAnimation = null; + } + } + }); + anim.setInterpolator(new DecelerateInterpolator()); + anim.start(); + mColorAnimation = anim; + } else { + mMainColor = mainColor; + invalidateSelf(); + } + } + + @Override + public void setAlpha(int alpha) { + if (alpha != mAlpha) { + mAlpha = alpha; + invalidateSelf(); + } + } + + @Override + public int getAlpha() { + return mAlpha; + } + + @Override + public void setXfermode(@Nullable Xfermode mode) { + mPaint.setXfermode(mode); + invalidateSelf(); + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + mPaint.setColorFilter(colorFilter); + } + + @Override + public ColorFilter getColorFilter() { + return mPaint.getColorFilter(); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + /** + * Enable drawable shape to have rounded corners with provided radius + */ + public void setRoundedCorners(float radius) { + mCornerRadius = radius; + } + + /** + * Make bottom edge concave with provided corner radius + */ + public void setBottomEdgeConcave(float radius) { + if (radius == 0) { + // Disable clipping completely when there's no radius. + mConcaveInfo = null; + return; + } + // only rounding top corners for clip out path + float[] cornerRadii = new float[]{radius, radius, radius, radius, 0, 0, 0, 0}; + mConcaveInfo = new ConcaveInfo(radius, cornerRadii); + } + + /** + * Location of concave edge. + * @see #setBottomEdgeConcave(float) + */ + public void setBottomEdgePosition(int y) { + if (mBottomEdgePosition == y) { + return; + } + mBottomEdgePosition = y; + if (mConcaveInfo == null) { + return; + } + updatePath(); + invalidateSelf(); + } + + @Override + public void draw(@NonNull Canvas canvas) { + mPaint.setColor(mMainColor); + mPaint.setAlpha(mAlpha); + if (mConcaveInfo != null) { + drawConcave(canvas); + } else { + canvas.drawRoundRect(getBounds().left, getBounds().top, getBounds().right, + getBounds().bottom + mCornerRadius, + /* x radius*/ mCornerRadius, /* y radius*/ mCornerRadius, mPaint); + } + } + + private void drawConcave(Canvas canvas) { + // checking if width of clip out path needs to change + if (mBounds == null + || getBounds().right != mBounds.right + || getBounds().left != mBounds.left) { + mBounds = getBounds(); + updatePath(); + } + canvas.clipOutPath(mConcaveInfo.mPath); + canvas.drawRect(getBounds().left, getBounds().top, getBounds().right, + mBottomEdgePosition + mConcaveInfo.mPathOverlap, mPaint); + } + + private void updatePath() { + mConcaveInfo.mPath.reset(); + if (mBounds == null) { + mBounds = getBounds(); + } + float top = mBottomEdgePosition; + float bottom = mBottomEdgePosition + mConcaveInfo.mPathOverlap; + mConcaveInfo.mPath.addRoundRect(mBounds.left, top, mBounds.right, bottom, + mConcaveInfo.mCornerRadii, Path.Direction.CW); + } + + @VisibleForTesting + public int getMainColor() { + return mMainColor; + } + + private static class ConcaveInfo { + private final float mPathOverlap; + private final float[] mCornerRadii; + private final Path mPath = new Path(); + + ConcaveInfo(float pathOverlap, float[] cornerRadii) { + mPathOverlap = pathOverlap; + mCornerRadii = cornerRadii; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java new file mode 100644 index 000000000000..0d9ade6da49c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2021 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.scrim; + +import static java.lang.Float.isNaN; + +import android.annotation.NonNull; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffColorFilter; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Looper; +import android.util.AttributeSet; +import android.view.View; + +import androidx.annotation.DimenRes; +import androidx.annotation.Nullable; +import androidx.core.graphics.ColorUtils; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.colorextraction.ColorExtractor; +import com.android.systemui.R; + +import java.util.concurrent.Executor; + + +/** + * A view which can draw a scrim. This view maybe be used in multiple windows running on different + * threads, but is controlled by {@link com.android.systemui.statusbar.phone.ScrimController} so we + * need to be careful to synchronize when necessary. + */ +public class ScrimView extends View { + + @DimenRes + private static final int CORNER_RADIUS = R.dimen.notification_scrim_corner_radius; + + private final Object mColorLock = new Object(); + + @GuardedBy("mColorLock") + private final ColorExtractor.GradientColors mColors; + // Used only for returning the colors + private final ColorExtractor.GradientColors mTmpColors = new ColorExtractor.GradientColors(); + private float mViewAlpha = 1.0f; + private Drawable mDrawable; + private PorterDuffColorFilter mColorFilter; + private int mTintColor; + private Runnable mChangeRunnable; + private Executor mChangeRunnableExecutor; + private Executor mExecutor; + private Looper mExecutorLooper; + @Nullable + private Rect mDrawableBounds; + + public ScrimView(Context context) { + this(context, null); + } + + public ScrimView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ScrimView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ScrimView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + mDrawable = new ScrimDrawable(); + mDrawable.setCallback(this); + mColors = new ColorExtractor.GradientColors(); + mExecutorLooper = Looper.myLooper(); + mExecutor = Runnable::run; + executeOnExecutor(() -> { + updateColorWithTint(false); + }); + } + + /** + * Needed for WM Shell, which has its own thread structure. + */ + public void setExecutor(Executor executor, Looper looper) { + mExecutor = executor; + mExecutorLooper = looper; + } + + @Override + protected void onDraw(Canvas canvas) { + if (mDrawable.getAlpha() > 0) { + mDrawable.draw(canvas); + } + } + + @VisibleForTesting + void setDrawable(Drawable drawable) { + executeOnExecutor(() -> { + mDrawable = drawable; + mDrawable.setCallback(this); + mDrawable.setBounds(getLeft(), getTop(), getRight(), getBottom()); + mDrawable.setAlpha((int) (255 * mViewAlpha)); + invalidate(); + }); + } + + @Override + public void invalidateDrawable(@NonNull Drawable drawable) { + super.invalidateDrawable(drawable); + if (drawable == mDrawable) { + invalidate(); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if (mDrawableBounds != null) { + mDrawable.setBounds(mDrawableBounds); + } else if (changed) { + mDrawable.setBounds(left, top, right, bottom); + invalidate(); + } + } + + @Override + public void setClickable(boolean clickable) { + executeOnExecutor(() -> { + super.setClickable(clickable); + }); + } + + /** + * Sets the color of the scrim, without animating them. + */ + public void setColors(@NonNull ColorExtractor.GradientColors colors) { + setColors(colors, false); + } + + /** + * Sets the scrim colors, optionally animating them. + * @param colors The colors. + * @param animated If we should animate the transition. + */ + public void setColors(@NonNull ColorExtractor.GradientColors colors, boolean animated) { + if (colors == null) { + throw new IllegalArgumentException("Colors cannot be null"); + } + executeOnExecutor(() -> { + synchronized (mColorLock) { + if (mColors.equals(colors)) { + return; + } + mColors.set(colors); + } + updateColorWithTint(animated); + }); + } + + @VisibleForTesting + Drawable getDrawable() { + return mDrawable; + } + + /** + * Returns current scrim colors. + */ + public ColorExtractor.GradientColors getColors() { + synchronized (mColorLock) { + mTmpColors.set(mColors); + } + return mTmpColors; + } + + /** + * Applies tint to this view, without animations. + */ + public void setTint(int color) { + setTint(color, false); + } + + /** + * Tints this view, optionally animating it. + * @param color The color. + * @param animated If we should animate. + */ + public void setTint(int color, boolean animated) { + executeOnExecutor(() -> { + if (mTintColor == color) { + return; + } + mTintColor = color; + updateColorWithTint(animated); + }); + } + + private void updateColorWithTint(boolean animated) { + if (mDrawable instanceof ScrimDrawable) { + // Optimization to blend colors and avoid a color filter + ScrimDrawable drawable = (ScrimDrawable) mDrawable; + float tintAmount = Color.alpha(mTintColor) / 255f; + int mainTinted = ColorUtils.blendARGB(mColors.getMainColor(), mTintColor, + tintAmount); + drawable.setColor(mainTinted, animated); + } else { + boolean hasAlpha = Color.alpha(mTintColor) != 0; + if (hasAlpha) { + PorterDuff.Mode targetMode = mColorFilter == null + ? Mode.SRC_OVER : mColorFilter.getMode(); + if (mColorFilter == null || mColorFilter.getColor() != mTintColor) { + mColorFilter = new PorterDuffColorFilter(mTintColor, targetMode); + } + } else { + mColorFilter = null; + } + + mDrawable.setColorFilter(mColorFilter); + mDrawable.invalidateSelf(); + } + + if (mChangeRunnable != null) { + mChangeRunnableExecutor.execute(mChangeRunnable); + } + } + + public int getTint() { + return mTintColor; + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } + + /** + * It might look counterintuitive to have another method to set the alpha instead of + * only using {@link #setAlpha(float)}. In this case we're in a hardware layer + * optimizing blend modes, so it makes sense. + * + * @param alpha Gradient alpha from 0 to 1. + */ + public void setViewAlpha(float alpha) { + if (isNaN(alpha)) { + throw new IllegalArgumentException("alpha cannot be NaN: " + alpha); + } + executeOnExecutor(() -> { + if (alpha != mViewAlpha) { + mViewAlpha = alpha; + + mDrawable.setAlpha((int) (255 * alpha)); + if (mChangeRunnable != null) { + mChangeRunnableExecutor.execute(mChangeRunnable); + } + } + }); + } + + public float getViewAlpha() { + return mViewAlpha; + } + + /** + * Sets a callback that is invoked whenever the alpha, color, or tint change. + */ + public void setChangeRunnable(Runnable changeRunnable, Executor changeRunnableExecutor) { + mChangeRunnable = changeRunnable; + mChangeRunnableExecutor = changeRunnableExecutor; + } + + @Override + protected boolean canReceivePointerEvents() { + return false; + } + + private void executeOnExecutor(Runnable r) { + if (mExecutor == null || Looper.myLooper() == mExecutorLooper) { + r.run(); + } else { + mExecutor.execute(r); + } + } + + /** + * Make bottom edge concave so overlap between layers is not visible for alphas between 0 and 1 + * @return height of concavity + */ + public float enableBottomEdgeConcave(boolean clipScrim) { + if (mDrawable instanceof ScrimDrawable) { + float radius = clipScrim ? getResources().getDimensionPixelSize(CORNER_RADIUS) : 0; + ((ScrimDrawable) mDrawable).setBottomEdgeConcave(radius); + return radius; + } + return 0; + } + + /** + * The position of the bottom of the scrim, used for clipping. + * @see #enableBottomEdgeConcave(boolean) + */ + public void setBottomEdgePosition(int y) { + if (mDrawable instanceof ScrimDrawable) { + ((ScrimDrawable) mDrawable).setBottomEdgePosition(y); + } + } + + /** + * Enable view to have rounded corners with radius of {@link #CORNER_RADIUS} + */ + public void enableRoundedCorners() { + if (mDrawable instanceof ScrimDrawable) { + int radius = getResources().getDimensionPixelSize(CORNER_RADIUS); + ((ScrimDrawable) mDrawable).setRoundedCorners(radius); + } + } + + /** + * Set bounds for the view, all coordinates are absolute + */ + public void setDrawableBounds(float left, float top, float right, float bottom) { + if (mDrawableBounds == null) { + mDrawableBounds = new Rect(); + } + mDrawableBounds.set((int) left, (int) top, (int) right, (int) bottom); + mDrawable.setBounds(mDrawableBounds); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java deleted file mode 100644 index a537299d4979..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java +++ /dev/null @@ -1,308 +0,0 @@ -/* - * Copyright (C) 2014 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; - -import static java.lang.Float.isNaN; - -import android.annotation.NonNull; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.PorterDuff.Mode; -import android.graphics.PorterDuffColorFilter; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.os.Looper; -import android.util.AttributeSet; -import android.view.View; - -import androidx.annotation.DimenRes; -import androidx.annotation.Nullable; -import androidx.core.graphics.ColorUtils; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.colorextraction.ColorExtractor; -import com.android.internal.colorextraction.drawable.ScrimDrawable; -import com.android.systemui.R; - -import java.util.concurrent.Executor; - - -/** - * A view which can draw a scrim. This view maybe be used in multiple windows running on different - * threads, but is controlled by {@link com.android.systemui.statusbar.phone.ScrimController} so we - * need to be careful to synchronize when necessary. - */ -public class ScrimView extends View { - - @DimenRes - private static final int CORNER_RADIUS = R.dimen.notification_scrim_corner_radius; - - private final Object mColorLock = new Object(); - - @GuardedBy("mColorLock") - private final ColorExtractor.GradientColors mColors; - // Used only for returning the colors - private final ColorExtractor.GradientColors mTmpColors = new ColorExtractor.GradientColors(); - private float mViewAlpha = 1.0f; - private Drawable mDrawable; - private PorterDuffColorFilter mColorFilter; - private int mTintColor; - private Runnable mChangeRunnable; - private Executor mChangeRunnableExecutor; - private Executor mExecutor; - private Looper mExecutorLooper; - @Nullable - private Rect mDrawableBounds; - - public ScrimView(Context context) { - this(context, null); - } - - public ScrimView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ScrimView(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public ScrimView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - - mDrawable = new ScrimDrawable(); - mDrawable.setCallback(this); - mColors = new ColorExtractor.GradientColors(); - mExecutorLooper = Looper.myLooper(); - mExecutor = Runnable::run; - executeOnExecutor(() -> { - updateColorWithTint(false); - }); - } - - public void setExecutor(Executor executor, Looper looper) { - mExecutor = executor; - mExecutorLooper = looper; - } - - @Override - protected void onDraw(Canvas canvas) { - if (mDrawable.getAlpha() > 0) { - mDrawable.draw(canvas); - } - } - - public void setDrawable(Drawable drawable) { - executeOnExecutor(() -> { - mDrawable = drawable; - mDrawable.setCallback(this); - mDrawable.setBounds(getLeft(), getTop(), getRight(), getBottom()); - mDrawable.setAlpha((int) (255 * mViewAlpha)); - invalidate(); - }); - } - - @Override - public void invalidateDrawable(@NonNull Drawable drawable) { - super.invalidateDrawable(drawable); - if (drawable == mDrawable) { - invalidate(); - } - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - if (mDrawableBounds != null) { - mDrawable.setBounds(mDrawableBounds); - } else if (changed) { - mDrawable.setBounds(left, top, right, bottom); - invalidate(); - } - } - - @Override - public void setClickable(boolean clickable) { - executeOnExecutor(() -> { - super.setClickable(clickable); - }); - } - - public void setColors(@NonNull ColorExtractor.GradientColors colors) { - setColors(colors, false); - } - - public void setColors(@NonNull ColorExtractor.GradientColors colors, boolean animated) { - if (colors == null) { - throw new IllegalArgumentException("Colors cannot be null"); - } - executeOnExecutor(() -> { - synchronized(mColorLock) { - if (mColors.equals(colors)) { - return; - } - mColors.set(colors); - } - updateColorWithTint(animated); - }); - } - - @VisibleForTesting - Drawable getDrawable() { - return mDrawable; - } - - public ColorExtractor.GradientColors getColors() { - synchronized(mColorLock) { - mTmpColors.set(mColors); - } - return mTmpColors; - } - - public void setTint(int color) { - setTint(color, false); - } - - public void setTint(int color, boolean animated) { - executeOnExecutor(() -> { - if (mTintColor == color) { - return; - } - mTintColor = color; - updateColorWithTint(animated); - }); - } - - private void updateColorWithTint(boolean animated) { - if (mDrawable instanceof ScrimDrawable) { - // Optimization to blend colors and avoid a color filter - ScrimDrawable drawable = (ScrimDrawable) mDrawable; - float tintAmount = Color.alpha(mTintColor) / 255f; - int mainTinted = ColorUtils.blendARGB(mColors.getMainColor(), mTintColor, - tintAmount); - drawable.setColor(mainTinted, animated); - } else { - boolean hasAlpha = Color.alpha(mTintColor) != 0; - if (hasAlpha) { - PorterDuff.Mode targetMode = mColorFilter == null ? Mode.SRC_OVER : - mColorFilter.getMode(); - if (mColorFilter == null || mColorFilter.getColor() != mTintColor) { - mColorFilter = new PorterDuffColorFilter(mTintColor, targetMode); - } - } else { - mColorFilter = null; - } - - mDrawable.setColorFilter(mColorFilter); - mDrawable.invalidateSelf(); - } - - if (mChangeRunnable != null) { - mChangeRunnableExecutor.execute(mChangeRunnable); - } - } - - public int getTint() { - return mTintColor; - } - - @Override - public boolean hasOverlappingRendering() { - return false; - } - - /** - * It might look counterintuitive to have another method to set the alpha instead of - * only using {@link #setAlpha(float)}. In this case we're in a hardware layer - * optimizing blend modes, so it makes sense. - * - * @param alpha Gradient alpha from 0 to 1. - */ - public void setViewAlpha(float alpha) { - if (isNaN(alpha)) { - throw new IllegalArgumentException("alpha cannot be NaN: " + alpha); - } - executeOnExecutor(() -> { - if (alpha != mViewAlpha) { - mViewAlpha = alpha; - - mDrawable.setAlpha((int) (255 * alpha)); - if (mChangeRunnable != null) { - mChangeRunnableExecutor.execute(mChangeRunnable); - } - } - }); - } - - public float getViewAlpha() { - return mViewAlpha; - } - - public void setChangeRunnable(Runnable changeRunnable, Executor changeRunnableExecutor) { - mChangeRunnable = changeRunnable; - mChangeRunnableExecutor = changeRunnableExecutor; - } - - @Override - protected boolean canReceivePointerEvents() { - return false; - } - - private void executeOnExecutor(Runnable r) { - if (mExecutor == null || Looper.myLooper() == mExecutorLooper) { - r.run(); - } else { - mExecutor.execute(r); - } - } - - /** - * Make bottom edge concave so overlap between layers is not visible for alphas between 0 and 1 - * @return height of concavity - */ - public float enableBottomEdgeConcave() { - if (mDrawable instanceof ScrimDrawable) { - float radius = getResources().getDimensionPixelSize(CORNER_RADIUS); - ((ScrimDrawable) mDrawable).setBottomEdgeConcave(radius); - return radius; - } - return 0; - } - - /** - * Enable view to have rounded corners with radius of {@link #CORNER_RADIUS} - */ - public void enableRoundedCorners() { - if (mDrawable instanceof ScrimDrawable) { - int radius = getResources().getDimensionPixelSize(CORNER_RADIUS); - ((ScrimDrawable) mDrawable).setRoundedCorners(radius); - } - } - - /** - * Set bounds for the view, all coordinates are absolute - */ - public void setDrawableBounds(float left, float top, float right, float bottom) { - if (mDrawableBounds == null) { - mDrawableBounds = new Rect(); - } - mDrawableBounds.set((int) left, (int) top, (int) right, (int) bottom); - mDrawable.setBounds(mDrawableBounds); - } -} 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 4fc49ed26f64..36a370c71216 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 @@ -780,7 +780,7 @@ public class NotificationStackScrollLayoutController { return mView.isLayoutRtl(); } - public float getLeft() { + public int getLeft() { return mView.getLeft(); } 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 961699e9e600..e2af940e66a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -515,6 +515,7 @@ public class NotificationPanelViewController extends PanelViewController { private boolean mDelayShowingKeyguardStatusBar; private boolean mAnimatingQS; + private final Rect mKeyguardStatusAreaClipBounds = new Rect(); private int mOldLayoutDirection; private NotificationShelfController mNotificationShelfController; @@ -522,6 +523,7 @@ public class NotificationPanelViewController extends PanelViewController { private final Executor mUiExecutor; private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL; + private int mScrimCornerRadius; private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() { @Override @@ -628,6 +630,7 @@ public class NotificationPanelViewController extends PanelViewController { mDozeParameters = dozeParameters; mBiometricUnlockController = biometricUnlockController; mScrimController = scrimController; + mScrimController.setClipsQsScrim(!mShouldUseSplitNotificationShade); mUserManager = userManager; mMediaDataManager = mediaDataManager; mQuickAccessWalletClient = quickAccessWalletClient; @@ -852,10 +855,16 @@ public class NotificationPanelViewController extends PanelViewController { public void updateResources() { mSplitShadeNotificationsTopPadding = mResources.getDimensionPixelSize(R.dimen.notifications_top_padding_split_shade); + mScrimCornerRadius = + mResources.getDimensionPixelSize(R.dimen.notification_scrim_corner_radius); int qsWidth = mResources.getDimensionPixelSize(R.dimen.qs_panel_width); int panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width); mShouldUseSplitNotificationShade = Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources); + mScrimController.setClipsQsScrim(!mShouldUseSplitNotificationShade); + if (mQs != null) { + mQs.setTranslateWhileExpanding(mShouldUseSplitNotificationShade); + } // To change the constraints at runtime, all children of the ConstraintLayout must have ids ensureAllViewsHaveIds(mNotificationContainerParent); ConstraintSet constraintSet = new ConstraintSet(); @@ -2003,15 +2012,23 @@ public class NotificationPanelViewController extends PanelViewController { mDepthController.setQsPanelExpansion(qsExpansionFraction); } - private void setNotificationBounds(float qsExpansionFraction, int qsPanelBottomY) { - float top = 0; - float bottom = 0; - float left = 0; - float right = 0; - if (qsPanelBottomY > 0) { - // notification shade is expanding/expanded + /** + * Updates scrim bounds, QS clipping, and KSV clipping as well based on the bounds of the shade + * and QS state. + * + * @param qsFraction QS expansion fraction, from getQsExpansionFraction(). + * @param qsPanelBottomY Absolute y position of the bottom of QS as it's being pulled. + */ + private void setNotificationBounds(float qsFraction, int qsPanelBottomY) { + int top = 0; + int bottom = 0; + int left = 0; + int right = 0; + boolean visible = qsFraction > 0 || qsPanelBottomY > 0; + if (visible || !mShouldUseSplitNotificationShade) { if (!mShouldUseSplitNotificationShade) { - top = qsPanelBottomY; + float notificationTop = mAmbientState.getStackY() - mQsNotificationTopPadding; + top = (int) Math.min(qsPanelBottomY, notificationTop); bottom = getView().getBottom(); left = getView().getLeft(); right = getView().getRight(); @@ -2022,6 +2039,17 @@ public class NotificationPanelViewController extends PanelViewController { right = mNotificationStackScrollLayoutController.getRight(); } } + + if (!mShouldUseSplitNotificationShade) { + // Fancy clipping for quick settings + if (mQs != null) { + mQs.setFancyClipping(top, bottom, mScrimCornerRadius, visible); + } + // 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); + } mScrimController.setNotificationsBounds(left, top, right, bottom); } @@ -2493,6 +2521,10 @@ public class NotificationPanelViewController extends PanelViewController { float appearAmount = mNotificationStackScrollLayoutController .calculateAppearFraction(mExpandedHeight); float startHeight = -mQsExpansionHeight; + if (!mShouldUseSplitNotificationShade && mBarState == StatusBarState.SHADE) { + // Small parallax as we pull down and clip QS + startHeight = -mQsExpansionHeight * 0.2f; + } if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard() && mNotificationStackScrollLayoutController.isPulseExpanding()) { if (!mPulseExpansionHandler.isExpanding() @@ -3128,8 +3160,10 @@ public class NotificationPanelViewController extends PanelViewController { mQs.setPanelView(mHeightListener); mQs.setExpandClickListener(mOnClickListener); mQs.setHeaderClickable(mQsExpansionEnabled); + mQs.setTranslateWhileExpanding(mShouldUseSplitNotificationShade); updateQSPulseExpansion(); mQs.setOverscrolling(mStackScrollerOverscrolling); + mQs.setTranslateWhileExpanding(mShouldUseSplitNotificationShade); // recompute internal state when qspanel height changes mQs.getView().addOnLayoutChangeListener( @@ -3925,7 +3959,10 @@ public class NotificationPanelViewController extends PanelViewController { // animate out // the top of QS if (!mQsExpanded) { - mQs.animateHeaderSlidingOut(); + // TODO(b/185683835) Nicer clipping when using new spacial model + if (mShouldUseSplitNotificationShade) { + mQs.animateHeaderSlidingOut(); + } } } else { mKeyguardStatusBar.setAlpha(1f); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 064086a828cb..5a2a6f21f94a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -147,9 +147,6 @@ public abstract class PanelViewController { private float mInitialTouchX; private boolean mTouchDisabled; - // AmbientState will never be null since it provides an @Inject constructor for Dagger to call. - private AmbientState mAmbientState; - /** * Whether or not the PanelView can be expanded or collapsed with a drag. */ @@ -172,6 +169,7 @@ public abstract class PanelViewController { protected final Resources mResources; protected final KeyguardStateController mKeyguardStateController; protected final SysuiStatusBarStateController mStatusBarStateController; + protected final AmbientState mAmbientState; protected void onExpandingFinished() { mBar.onExpandingFinished(); 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 5e9c758da07a..0d96ead964f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -48,8 +48,8 @@ import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dock.DockManager; +import com.android.systemui.scrim.ScrimView; import com.android.systemui.statusbar.FeatureFlags; -import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.notification.stack.ViewState; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -96,6 +96,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump * When at least 1 scrim is fully opaque (alpha set to 1.) */ public static final int OPAQUE = 2; + private boolean mClipsQsScrim; @IntDef(prefix = {"VISIBILITY_"}, value = { TRANSPARENT, @@ -165,6 +166,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump // Assuming the shade is expanded during initialization private float mExpansionFraction = 1f; private float mQsExpansion; + private boolean mQsBottomVisible; private boolean mDarkenWhileDragging; private boolean mExpansionAffectsAlpha = true; @@ -183,6 +185,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private int mInFrontTint; private int mBehindTint; + private int mNotificationsTint; private int mBubbleTint; private boolean mWallpaperVisibilityTimedOut; @@ -265,6 +268,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mScrimForBubble = scrimForBubble; updateThemeColors(); + behindScrim.enableBottomEdgeConcave(mClipsQsScrim); mNotificationsScrim.enableRoundedCorners(); if (mScrimBehindChangeRunnable != null) { @@ -331,14 +335,17 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mInFrontTint = state.getFrontTint(); mBehindTint = state.getBehindTint(); + mNotificationsTint = state.getNotifTint(); mBubbleTint = state.getBubbleTint(); mInFrontAlpha = state.getFrontAlpha(); mBehindAlpha = state.getBehindAlpha(); mBubbleAlpha = state.getBubbleAlpha(); - if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha)) { + mNotificationsAlpha = state.getNotifAlpha(); + if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) { throw new IllegalStateException("Scrim opacity is NaN for state: " + state + ", front: " - + mInFrontAlpha + ", back: " + mBehindAlpha); + + mInFrontAlpha + ", back: " + mBehindAlpha + ", notif: " + + mNotificationsAlpha); } applyExpansionToAlpha(); @@ -397,7 +404,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump scheduleUpdate(); } - dispatchScrimState(mScrimBehind.getViewAlpha()); + dispatchBackScrimState(mScrimBehind.getViewAlpha()); } private boolean shouldFadeAwayWallpaper() { @@ -491,21 +498,25 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump */ public void setNotificationsBounds(float left, float top, float right, float bottom) { mNotificationsScrim.setDrawableBounds(left, top, right, bottom); + if (mClipsQsScrim) { + mScrimBehind.setBottomEdgePosition((int) top); + } } /** * Current state of the QuickSettings when pulling it from the top. * * @param expansionFraction From 0 to 1 where 0 means collapsed and 1 expanded. - * @param qsPanelBottomY absolute Y position of qs panel bottom + * @param qsPanelBottomY Absolute Y position of qs panel bottom */ public void setQsPosition(float expansionFraction, int qsPanelBottomY) { if (isNaN(expansionFraction)) { return; } - updateNotificationsScrimAlpha(expansionFraction, qsPanelBottomY); - if (mQsExpansion != expansionFraction) { + boolean qsBottomVisible = qsPanelBottomY > 0; + if (mQsExpansion != expansionFraction || mQsBottomVisible != qsBottomVisible) { mQsExpansion = expansionFraction; + mQsBottomVisible = qsBottomVisible; boolean relevantState = (mState == ScrimState.SHADE_LOCKED || mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING @@ -517,31 +528,36 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } } - private void updateNotificationsScrimAlpha(float qsExpansion, int qsPanelBottomY) { - float newAlpha = 0; - if (qsPanelBottomY > 0) { - float interpolator = 0; - if (mState == ScrimState.UNLOCKED || mState == ScrimState.SHADE_LOCKED) { - interpolator = getInterpolatedFraction(); - } else { - interpolator = qsExpansion; - } - newAlpha = MathUtils.lerp(0, 1, interpolator); + /** + * If QS and notification scrims should not overlap, and should be clipped to each other's + * bounds instead. + */ + public void setClipsQsScrim(boolean clipScrim) { + if (clipScrim == mClipsQsScrim) { + return; } - if (newAlpha != mNotificationsAlpha) { - mNotificationsAlpha = newAlpha; - // update alpha without animating - mNotificationsScrim.setViewAlpha(newAlpha); + mClipsQsScrim = clipScrim; + for (ScrimState state : ScrimState.values()) { + state.setClipQsScrim(mClipsQsScrim); + } + if (mScrimBehind != null) { + mScrimBehind.enableBottomEdgeConcave(mClipsQsScrim); } } + @VisibleForTesting + public boolean getClipQsScrim() { + return mClipsQsScrim; + } + private void setOrAdaptCurrentAnimation(@Nullable View scrim) { if (scrim == null) { return; } float alpha = getCurrentScrimAlpha(scrim); - if (isAnimating(scrim)) { + boolean qsScrimPullingDown = scrim == mScrimBehind && mQsBottomVisible; + if (isAnimating(scrim) && !qsScrimPullingDown) { // Adapt current animation. ValueAnimator previousAnimator = (ValueAnimator) scrim.getTag(TAG_KEY_ANIM); float previousEndValue = (Float) scrim.getTag(TAG_END_ALPHA); @@ -562,7 +578,19 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump return; } - if (mState == ScrimState.UNLOCKED || mState == ScrimState.BUBBLE_EXPANDED) { + if (mState == ScrimState.UNLOCKED) { + // Darken scrim as you pull down the shade when unlocked + float behindFraction = getInterpolatedFraction(); + behindFraction = (float) Math.pow(behindFraction, 0.8f); + if (mClipsQsScrim) { + mBehindAlpha = 1; + mNotificationsAlpha = behindFraction * mDefaultScrimAlpha; + } else { + mBehindAlpha = behindFraction * mDefaultScrimAlpha; + mNotificationsAlpha = mBehindAlpha; + } + mInFrontAlpha = 0; + } else if (mState == ScrimState.BUBBLE_EXPANDED) { // Darken scrim as you pull down the shade when unlocked float behindFraction = getInterpolatedFraction(); behindFraction = (float) Math.pow(behindFraction, 0.8f); @@ -573,27 +601,45 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump // Either darken of make the scrim transparent when you // pull down the shade float interpolatedFract = getInterpolatedFraction(); - float alphaBehind = mState.getBehindAlpha(); + float stateBehind = mClipsQsScrim ? mState.getNotifAlpha() : mState.getBehindAlpha(); + float backAlpha; if (mDarkenWhileDragging) { - mBehindAlpha = MathUtils.lerp(mDefaultScrimAlpha, alphaBehind, + backAlpha = MathUtils.lerp(mDefaultScrimAlpha, stateBehind, interpolatedFract); - mInFrontAlpha = mState.getFrontAlpha(); } else { - mBehindAlpha = MathUtils.lerp(0 /* start */, alphaBehind, + backAlpha = MathUtils.lerp(0 /* start */, stateBehind, interpolatedFract); - mInFrontAlpha = mState.getFrontAlpha(); } - mBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(), - mState.getBehindTint(), interpolatedFract); + 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) { - mBehindAlpha = MathUtils.lerp(mBehindAlpha, mDefaultScrimAlpha, mQsExpansion); - mBehindTint = ColorUtils.blendARGB(mBehindTint, - ScrimState.SHADE_LOCKED.getBehindTint(), mQsExpansion); + 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; + mBehindAlpha = 1; + mBehindTint = Color.BLACK; + } else { + mBehindAlpha = backAlpha; + mNotificationsAlpha = Math.max(1.0f - getInterpolatedFraction(), mQsExpansion); + mBehindTint = backTint; } } - if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha)) { + if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) { throw new IllegalStateException("Scrim opacity is NaN for state: " + mState - + ", front: " + mInFrontAlpha + ", back: " + mBehindAlpha); + + ", front: " + mInFrontAlpha + ", back: " + mBehindAlpha + ", notif: " + + mNotificationsAlpha); } } @@ -606,7 +652,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump setOrAdaptCurrentAnimation(mNotificationsScrim); setOrAdaptCurrentAnimation(mScrimInFront); setOrAdaptCurrentAnimation(mScrimForBubble); - dispatchScrimState(mScrimBehind.getViewAlpha()); + dispatchBackScrimState(mScrimBehind.getViewAlpha()); // Reset wallpaper timeout if it's already timeout like expanding panel while PULSING // and docking. @@ -693,10 +739,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump && !mBlankScreen; mScrimInFront.setColors(mColors, animateScrimInFront); - mScrimBehind.setColors(mColors, animateScrimNotifications); + mScrimBehind.setColors(mColors, animateBehindScrim); mNotificationsScrim.setColors(mColors, animateScrimNotifications); - dispatchScrimState(mScrimBehind.getViewAlpha()); + dispatchBackScrimState(mScrimBehind.getViewAlpha()); } // We want to override the back scrim opacity for the AOD state @@ -723,15 +769,21 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump dispatchScrimsVisible(); } - private void dispatchScrimState(float alpha) { + private void dispatchBackScrimState(float alpha) { + // When clipping QS, the notification scrim is the one that feels behind. + // mScrimBehind will be drawing black and its opacity will always be 1. + if (mClipsQsScrim && mQsBottomVisible) { + alpha = mNotificationsAlpha; + } mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors()); } private void dispatchScrimsVisible() { + final ScrimView backScrim = mClipsQsScrim ? mNotificationsScrim : mScrimBehind; final int currentScrimVisibility; - if (mScrimInFront.getViewAlpha() == 1 || mScrimBehind.getViewAlpha() == 1) { + if (mScrimInFront.getViewAlpha() == 1 || backScrim.getViewAlpha() == 1) { currentScrimVisibility = OPAQUE; - } else if (mScrimInFront.getViewAlpha() == 0 && mScrimBehind.getViewAlpha() == 0) { + } else if (mScrimInFront.getViewAlpha() == 0 && backScrim.getViewAlpha() == 0) { currentScrimVisibility = TRANSPARENT; } else { currentScrimVisibility = SEMI_TRANSPARENT; @@ -859,7 +911,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } else if (scrim == mScrimBehind) { return mBehindTint; } else if (scrim == mNotificationsScrim) { - return Color.TRANSPARENT; + return mNotificationsTint; } else if (scrim == mScrimForBubble) { return mBubbleTint; } else { @@ -917,9 +969,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump if (mState == ScrimState.UNLOCKED) { mInFrontTint = Color.TRANSPARENT; mBehindTint = mState.getBehindTint(); + mNotificationsTint = mState.getNotifTint(); mBubbleTint = Color.TRANSPARENT; updateScrimColor(mScrimInFront, mInFrontAlpha, mInFrontTint); updateScrimColor(mScrimBehind, mBehindAlpha, mBehindTint); + updateScrimColor(mNotificationsScrim, mNotificationsAlpha, mNotificationsTint); if (mScrimForBubble != null) { updateScrimColor(mScrimForBubble, mBubbleAlpha, mBubbleTint); } @@ -964,7 +1018,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } if (scrim == mScrimBehind) { - dispatchScrimState(alpha); + dispatchBackScrimState(alpha); } final boolean wantsAlphaUpdate = alpha != currentAlpha; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index a9774d850fd9..66cc26f5d295 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -22,7 +22,7 @@ import android.os.Trace; import androidx.annotation.Nullable; import com.android.systemui.dock.DockManager; -import com.android.systemui.statusbar.ScrimView; +import com.android.systemui.scrim.ScrimView; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; /** @@ -80,11 +80,16 @@ public enum ScrimState { } mFrontTint = Color.BLACK; mBehindTint = Color.BLACK; + mNotifTint = mClipQsScrim ? Color.BLACK : Color.TRANSPARENT; mBubbleTint = Color.TRANSPARENT; mFrontAlpha = 0; - mBehindAlpha = mScrimBehindAlphaKeyguard; + mBehindAlpha = mClipQsScrim ? 1 : mScrimBehindAlphaKeyguard; + mNotifAlpha = mClipQsScrim ? mScrimBehindAlphaKeyguard : 0; mBubbleAlpha = 0; + if (mClipQsScrim) { + updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK); + } } }, @@ -105,7 +110,10 @@ public enum ScrimState { BOUNCER { @Override public void prepare(ScrimState previousState) { - mBehindAlpha = mDefaultScrimAlpha; + mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha; + mBehindTint = mClipQsScrim ? Color.BLACK : Color.TRANSPARENT; + mNotifAlpha = mClipQsScrim ? mDefaultScrimAlpha : 0; + mNotifTint = Color.TRANSPARENT; mFrontAlpha = 0f; mBubbleAlpha = 0f; } @@ -126,10 +134,15 @@ public enum ScrimState { SHADE_LOCKED { @Override public void prepare(ScrimState previousState) { - mBehindAlpha = mDefaultScrimAlpha; + mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha; + mNotifAlpha = 1f; mBubbleAlpha = 0f; mFrontAlpha = 0f; mBehindTint = Color.BLACK; + + if (mClipQsScrim) { + updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK); + } } // to make sure correct color is returned before "prepare" is called @@ -224,7 +237,8 @@ public enum ScrimState { @Override public void prepare(ScrimState previousState) { // State that UI will sync to. - mBehindAlpha = 0; + mBehindAlpha = mClipQsScrim ? 1 : 0; + mNotifAlpha = 0; mFrontAlpha = 0; mBubbleAlpha = 0; @@ -253,6 +267,10 @@ public enum ScrimState { mBubbleTint = Color.BLACK; mBlankScreen = true; } + + if (mClipQsScrim) { + updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK); + } } }, @@ -279,12 +297,14 @@ public enum ScrimState { int mFrontTint = Color.TRANSPARENT; int mBehindTint = Color.TRANSPARENT; int mBubbleTint = Color.TRANSPARENT; + int mNotifTint = Color.TRANSPARENT; boolean mAnimateChange = true; float mAodFrontScrimAlpha; float mFrontAlpha; float mBehindAlpha; float mBubbleAlpha; + float mNotifAlpha; float mScrimBehindAlphaKeyguard; float mDefaultScrimAlpha; @@ -301,6 +321,7 @@ public enum ScrimState { boolean mWakeLockScreenSensorActive; boolean mKeyguardFadingAway; long mKeyguardFadingAwayDuration; + boolean mClipQsScrim; public void init(ScrimView scrimInFront, ScrimView scrimBehind, ScrimView scrimForBubble, DozeParameters dozeParameters, DockManager dockManager) { @@ -325,6 +346,10 @@ public enum ScrimState { return mBehindAlpha; } + public float getNotifAlpha() { + return mNotifAlpha; + } + public float getBubbleAlpha() { return mBubbleAlpha; } @@ -337,6 +362,10 @@ public enum ScrimState { return mBehindTint; } + public int getNotifTint() { + return mNotifTint; + } + public int getBubbleTint() { return mBubbleTint; } @@ -406,4 +435,8 @@ public enum ScrimState { mKeyguardFadingAway = fadingAway; mKeyguardFadingAwayDuration = duration; } + + public void setClipQsScrim(boolean clipsQsScrim) { + mClipQsScrim = clipsQsScrim; + } } \ No newline at end of file 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 026072bf0d6a..de448100d33f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -21,7 +21,6 @@ import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.WindowType; import static android.app.StatusBarManager.WindowVisibleState; import static android.app.StatusBarManager.windowStateToString; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.InsetsState.containsType; @@ -180,6 +179,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSFragment; import com.android.systemui.qs.QSPanelController; import com.android.systemui.recents.ScreenPinningRequest; +import com.android.systemui.scrim.ScrimView; import com.android.systemui.settings.brightness.BrightnessSlider; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.AutoHideUiElement; @@ -202,7 +202,6 @@ import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.PowerButtonReveal; import com.android.systemui.statusbar.PulseExpansionHandler; -import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index b823534c2813..b95545576e56 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -63,11 +63,11 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.scrim.ScrimView; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationShadeWindowController; -import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.notification.NotificationChannelHelper; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/scrim/ScrimViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/scrim/ScrimViewTest.java new file mode 100644 index 000000000000..a345f7804ae9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/scrim/ScrimViewTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2021 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.scrim; + +import static junit.framework.Assert.assertEquals; + +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; +import android.testing.ViewUtils; +import android.view.View; + +import androidx.test.filters.SmallTest; + +import com.android.internal.colorextraction.ColorExtractor; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.utils.leaks.LeakCheckedTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class ScrimViewTest extends LeakCheckedTest { + + ScrimView mView; + + @Before + public void setUp() { + injectLeakCheckedDependency(ConfigurationController.class); + mView = new ScrimView(getContext()); + mView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + mView.layout(0, 0, 1920, 1080); + } + + @Test + @RunWithLooper + public void testAttachDetach() { + ViewUtils.attachView(mView); + TestableLooper.get(this).processAllMessages(); + ViewUtils.detachView(mView); + TestableLooper.get(this).processAllMessages(); + } + + @Test + public void testSetDrawable_UpdateDrawable() { + Drawable drawable = new ColorDrawable(Color.GREEN); + mView.setDrawable(drawable); + assertEquals(drawable, mView.getDrawable()); + } + + @Test + public void testCreation_initialColor() { + ScrimDrawable drawable = (ScrimDrawable) mView.getDrawable(); + ColorExtractor.GradientColors colors = mView.getColors(); + assertEquals("Main color should be set upon creation", + drawable.getMainColor(), colors.getMainColor()); + } + + @Test + public void testSetViewAlpha_propagatesToDrawable() { + final float alpha = 0.5f; + mView.setViewAlpha(alpha); + assertEquals("View alpha did not propagate to drawable", alpha, mView.getViewAlpha()); + } + + @Test + public void setTint_set() { + int tint = Color.BLUE; + mView.setTint(tint); + assertEquals(mView.getTint(), tint); + } + + @Test + public void setDrawableBounds_propagatesToDrawable() { + ColorDrawable drawable = new ColorDrawable(); + Rect expectedBounds = new Rect(100, 100, 100, 100); + mView.setDrawable(drawable); + mView.setDrawableBounds(100, 100, 100, 100); + + assertEquals(expectedBounds, drawable.getBounds()); + // set bounds that are different from expected drawable bounds + mView.onLayout(true, 200, 200, 200, 200); + assertEquals(expectedBounds, drawable.getBounds()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java deleted file mode 100644 index c2e58efe1328..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2017 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; - -import static junit.framework.Assert.assertEquals; - -import android.graphics.Color; -import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.testing.TestableLooper.RunWithLooper; -import android.testing.ViewUtils; -import android.view.View; - -import androidx.test.filters.SmallTest; - -import com.android.internal.colorextraction.ColorExtractor; -import com.android.internal.colorextraction.drawable.ScrimDrawable; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.utils.leaks.LeakCheckedTest; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidTestingRunner.class) -@SmallTest -public class ScrimViewTest extends LeakCheckedTest { - - ScrimView mView; - - @Before - public void setUp() { - injectLeakCheckedDependency(ConfigurationController.class); - mView = new ScrimView(getContext()); - mView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - mView.layout(0, 0, 1920, 1080); - } - - @Test - @RunWithLooper - public void testAttachDetach() { - ViewUtils.attachView(mView); - TestableLooper.get(this).processAllMessages(); - ViewUtils.detachView(mView); - TestableLooper.get(this).processAllMessages(); - } - - @Test - public void testSetDrawable_UpdateDrawable() { - Drawable drawable = new ColorDrawable(Color.GREEN); - mView.setDrawable(drawable); - assertEquals(drawable, mView.getDrawable()); - } - - @Test - public void testCreation_initialColor() { - ScrimDrawable drawable = (ScrimDrawable) mView.getDrawable(); - ColorExtractor.GradientColors colors = mView.getColors(); - assertEquals("Main color should be set upon creation", - drawable.getMainColor(), colors.getMainColor()); - } - - @Test - public void testSetViewAlpha_propagatesToDrawable() { - final float alpha = 0.5f; - mView.setViewAlpha(alpha); - assertEquals("View alpha did not propagate to drawable", alpha, mView.getViewAlpha()); - } - - @Test - public void setTint_set() { - int tint = Color.BLUE; - mView.setTint(tint); - assertEquals(mView.getTint(), tint); - } - - @Test - public void setDrawableBounds_propagatesToDrawable() { - ColorDrawable drawable = new ColorDrawable(); - Rect expectedBounds = new Rect(100, 100, 100, 100); - mView.setDrawable(drawable); - mView.setDrawableBounds(100, 100, 100, 100); - - assertEquals(expectedBounds, drawable.getBounds()); - // set bounds that are different from expected drawable bounds - mView.onLayout(true, 200, 200, 200, 200); - assertEquals(expectedBounds, drawable.getBounds()); - } -} 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 8633eb466b6c..559ee6f29bb6 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 @@ -49,8 +49,8 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.DejankUtils; import com.android.systemui.SysuiTestCase; import com.android.systemui.dock.DockManager; +import com.android.systemui.scrim.ScrimView; import com.android.systemui.statusbar.FeatureFlags; -import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; @@ -69,6 +69,7 @@ import org.mockito.stubbing.Answer; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -91,7 +92,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Mock private AlarmManager mAlarmManager; @Mock - private DozeParameters mDozeParamenters; + private DozeParameters mDozeParameters; @Mock LightBarController mLightBarController; @Mock @@ -200,8 +201,8 @@ public class ScrimControllerTest extends SysuiTestCase { return null; }).when(mScrimBehind).postOnAnimationDelayed(any(Runnable.class), anyLong()); - when(mDozeParamenters.getAlwaysOn()).thenAnswer(invocation -> mAlwaysOnEnabled); - when(mDozeParamenters.getDisplayNeedsBlanking()).thenReturn(true); + when(mDozeParameters.getAlwaysOn()).thenAnswer(invocation -> mAlwaysOnEnabled); + when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true); doAnswer((Answer) invocation -> { mScrimState = invocation.getArgument(0); @@ -220,7 +221,7 @@ public class ScrimControllerTest extends SysuiTestCase { when(mDockManager.isDocked()).thenReturn(false); mScrimController = new ScrimController(mLightBarController, - mDozeParamenters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder, + mDozeParameters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder, new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor, mDockManager, mConfigurationController, mFeatureFlags, new FakeExecutor(new FakeSystemClock())); @@ -238,6 +239,10 @@ public class ScrimControllerTest extends SysuiTestCase { @After public void tearDown() { finishAnimationsImmediately(); + Arrays.stream(ScrimState.values()).forEach((scrim) -> { + scrim.setAodFrontScrimAlpha(0f); + scrim.setClipQsScrim(false); + }); DejankUtils.setImmediate(false); } @@ -259,9 +264,30 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void transitionToShadeLocked() { mScrimController.transitionTo(ScrimState.SHADE_LOCKED); + mScrimController.setQsPosition(1f, 0); + finishAnimationsImmediately(); + + assertScrimAlpha(Map.of( + mNotificationsScrim, OPAQUE, + mScrimInFront, TRANSPARENT, + mScrimBehind, OPAQUE)); + + assertScrimTinted(Map.of( + mScrimInFront, false, + mScrimBehind, true, + mScrimForBubble, false + )); + } + + @Test + public void transitionToShadeLocked_clippingQs() { + mScrimController.setClipsQsScrim(true); + mScrimController.transitionTo(ScrimState.SHADE_LOCKED); + mScrimController.setQsPosition(1f, 0); finishAnimationsImmediately(); assertScrimAlpha(Map.of( + mNotificationsScrim, OPAQUE, mScrimInFront, TRANSPARENT, mScrimBehind, OPAQUE)); @@ -403,9 +429,6 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimController.setAodFrontScrimAlpha(0.3f); Assert.assertEquals(ScrimState.AOD.getFrontAlpha(), mScrimInFront.getViewAlpha(), 0.001f); Assert.assertNotEquals(0.3f, mScrimInFront.getViewAlpha(), 0.001f); - - // Reset value since enums are static. - mScrimController.setAodFrontScrimAlpha(0f); } @Test @@ -500,11 +523,34 @@ public class ScrimControllerTest extends SysuiTestCase { // Back scrim should be visible without tint assertScrimAlpha(Map.of( mScrimInFront, TRANSPARENT, + mNotificationsScrim, TRANSPARENT, mScrimBehind, OPAQUE)); assertScrimTinted(Map.of( mScrimInFront, false, mScrimBehind, false, + mNotificationsScrim, false, + mScrimForBubble, false + )); + } + + @Test + public void transitionToKeyguardBouncer_clippingQs() { + mScrimController.setClipsQsScrim(true); + mScrimController.transitionTo(ScrimState.BOUNCER); + finishAnimationsImmediately(); + // Front scrim should be transparent + // Back scrim should be clipping QS + // Notif scrim should be visible without tint + assertScrimAlpha(Map.of( + mScrimInFront, TRANSPARENT, + mNotificationsScrim, OPAQUE, + mScrimBehind, OPAQUE)); + + assertScrimTinted(Map.of( + mScrimInFront, false, + mScrimBehind, true, + mNotificationsScrim, false, mScrimForBubble, false )); } @@ -530,9 +576,11 @@ public class ScrimControllerTest extends SysuiTestCase { finishAnimationsImmediately(); assertScrimAlpha(Map.of( mScrimInFront, TRANSPARENT, + mNotificationsScrim, TRANSPARENT, mScrimBehind, TRANSPARENT)); assertScrimTinted(Map.of( + mNotificationsScrim, false, mScrimInFront, false, mScrimBehind, true, mScrimForBubble, false @@ -542,6 +590,7 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimController.setPanelExpansion(0.5f); assertScrimAlpha(Map.of( mScrimInFront, TRANSPARENT, + mNotificationsScrim, SEMI_TRANSPARENT, mScrimBehind, SEMI_TRANSPARENT)); } @@ -616,6 +665,32 @@ public class ScrimControllerTest extends SysuiTestCase { mNotificationsScrim, OPAQUE)); } + @Test + public void qsExpansion_clippingQs() { + reset(mScrimBehind); + mScrimController.setClipsQsScrim(true); + mScrimController.setQsPosition(1f, 999 /* value doesn't matter */); + finishAnimationsImmediately(); + + assertScrimAlpha(Map.of( + mScrimInFront, TRANSPARENT, + mScrimBehind, OPAQUE, + mNotificationsScrim, OPAQUE)); + } + + @Test + public void qsExpansion_half_clippingQs() { + reset(mScrimBehind); + mScrimController.setClipsQsScrim(true); + mScrimController.setQsPosition(0.5f, 999 /* value doesn't matter */); + finishAnimationsImmediately(); + + assertScrimAlpha(Map.of( + mScrimInFront, TRANSPARENT, + mScrimBehind, OPAQUE, + mNotificationsScrim, SEMI_TRANSPARENT)); + } + @Test public void panelExpansionAffectsAlpha() { mScrimController.setPanelExpansion(0f); @@ -919,13 +994,13 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testAnimatesTransitionToAod() { - when(mDozeParamenters.shouldControlScreenOff()).thenReturn(false); + when(mDozeParameters.shouldControlScreenOff()).thenReturn(false); ScrimState.AOD.prepare(ScrimState.KEYGUARD); Assert.assertFalse("No animation when ColorFade kicks in", ScrimState.AOD.getAnimateChange()); - reset(mDozeParamenters); - when(mDozeParamenters.shouldControlScreenOff()).thenReturn(true); + reset(mDozeParameters); + when(mDozeParameters.shouldControlScreenOff()).thenReturn(true); ScrimState.AOD.prepare(ScrimState.KEYGUARD); Assert.assertTrue("Animate scrims when ColorFade won't be triggered", ScrimState.AOD.getAnimateChange()); @@ -977,6 +1052,7 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimController.setPanelExpansion(0.5f); // notifications scrim alpha change require calling setQsPosition mScrimController.setQsPosition(0, 300); + finishAnimationsImmediately(); assertScrimAlpha(Map.of( mScrimBehind, SEMI_TRANSPARENT, @@ -984,6 +1060,21 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimInFront, TRANSPARENT)); } + @Test + public void testScrimsVisible_whenShadeVisible_clippingQs() { + mScrimController.setClipsQsScrim(true); + mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.setPanelExpansion(0.5f); + // notifications scrim alpha change require calling setQsPosition + mScrimController.setQsPosition(0.5f, 300); + finishAnimationsImmediately(); + + assertScrimAlpha(Map.of( + mScrimBehind, OPAQUE, + mNotificationsScrim, SEMI_TRANSPARENT, + mScrimInFront, TRANSPARENT)); + } + @Test public void testScrimsVisible_whenShadeVisibleOnLockscreen() { mScrimController.transitionTo(ScrimState.KEYGUARD); @@ -1008,8 +1099,6 @@ public class ScrimControllerTest extends SysuiTestCase { } private void assertScrimTinted(Map scrimToTint) { - // notifications scrim should have always transparent tint - assertScrimTint(mNotificationsScrim, false); scrimToTint.forEach((scrim, hasTint) -> assertScrimTint(scrim, hasTint)); } @@ -1047,6 +1136,12 @@ public class ScrimControllerTest extends SysuiTestCase { } scrimToAlpha.forEach((scrimView, alpha) -> assertScrimAlpha(scrimView, alpha)); + // When clipping, QS scrim should not affect combined visibility. + if (mScrimController.getClipQsScrim() && scrimToAlpha.get(mScrimBehind) == OPAQUE) { + scrimToAlpha = new HashMap<>(scrimToAlpha); + scrimToAlpha.remove(mScrimBehind); + } + // Check combined scrim visibility. final int visibility; if (scrimToAlpha.values().contains(OPAQUE)) { -- cgit v1.2.3-59-g8ed1b