Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e5d559c2 authored by Jeff DeCew's avatar Jeff DeCew Committed by Android (Google) Code Review
Browse files

Merge changes I1f6e65ec,I109a8c0d,I46ff6129 into main

* changes:
  Remove dead code from NotificationSection
  Format NotificationSectionsManager.kt
  Ensure corners are rounded of prioroty conversations
parents 1696c462 89ccecb1
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -22,11 +22,13 @@ import android.provider.DeviceConfig
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
import com.android.systemui.statusbar.notification.stack.BUCKET_MEDIA_CONTROLS
import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
import com.android.systemui.statusbar.notification.stack.BUCKET_PRIORITY_PEOPLE
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.Utils
@@ -53,6 +55,18 @@ class NotificationSectionsFeatureManager @Inject constructor(
    }

    fun getNotificationBuckets(): IntArray {
        if (PriorityPeopleSection.isEnabled) {
            // We don't need this list to be adaptive, it can be the superset of all features.
            return intArrayOf(
                    BUCKET_MEDIA_CONTROLS,
                    BUCKET_HEADS_UP,
                    BUCKET_FOREGROUND_SERVICE,
                    BUCKET_PRIORITY_PEOPLE,
                    BUCKET_PEOPLE,
                    BUCKET_ALERTING,
                    BUCKET_SILENT,
                )
        }
        return when {
            isFilteringEnabled() && isMediaControlsEnabled() ->
                intArrayOf(BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, BUCKET_MEDIA_CONTROLS,
+1 −246
Original line number Diff line number Diff line
@@ -16,17 +16,6 @@

package com.android.systemui.statusbar.notification.stack;

import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.graphics.Rect;
import android.view.View;
import android.view.animation.Interpolator;

import com.android.app.animation.Interpolators;
import com.android.systemui.statusbar.notification.row.ExpandableView;

/**
@@ -35,165 +24,18 @@ import com.android.systemui.statusbar.notification.row.ExpandableView;
 */
public class NotificationSection {
    private @PriorityBucket final int mBucket;
    private final View mOwningView;
    private final Rect mBounds = new Rect();
    private final Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
    private final Rect mStartAnimationRect = new Rect();
    private final Rect mEndAnimationRect = new Rect();
    private ObjectAnimator mTopAnimator = null;
    private ObjectAnimator mBottomAnimator = null;
    private ExpandableView mFirstVisibleChild;
    private ExpandableView mLastVisibleChild;

    NotificationSection(View owningView, @PriorityBucket int bucket) {
        mOwningView = owningView;
    NotificationSection(@PriorityBucket int bucket) {
        mBucket = bucket;
    }

    public void cancelAnimators() {
        if (mBottomAnimator != null) {
            mBottomAnimator.cancel();
        }
        if (mTopAnimator != null) {
            mTopAnimator.cancel();
        }
    }

    public Rect getCurrentBounds() {
        return mCurrentBounds;
    }

    public Rect getBounds() {
        return mBounds;
    }

    public boolean didBoundsChange() {
        return !mCurrentBounds.equals(mBounds);
    }

    public boolean areBoundsAnimating() {
        return mBottomAnimator != null || mTopAnimator != null;
    }

    @PriorityBucket
    public int getBucket() {
        return mBucket;
    }

    public void startBackgroundAnimation(boolean animateTop, boolean animateBottom) {
        // Left and right bounds are always applied immediately.
        mCurrentBounds.left = mBounds.left;
        mCurrentBounds.right = mBounds.right;
        startBottomAnimation(animateBottom);
        startTopAnimation(animateTop);
    }


    private void startTopAnimation(boolean animate) {
        int previousEndValue = mEndAnimationRect.top;
        int newEndValue = mBounds.top;
        ObjectAnimator previousAnimator = mTopAnimator;
        if (previousAnimator != null && previousEndValue == newEndValue) {
            return;
        }
        if (!animate) {
            // just a local update was performed
            if (previousAnimator != null) {
                // we need to increase all animation keyframes of the previous animator by the
                // relative change to the end value
                int previousStartValue = mStartAnimationRect.top;
                PropertyValuesHolder[] values = previousAnimator.getValues();
                values[0].setIntValues(previousStartValue, newEndValue);
                mStartAnimationRect.top = previousStartValue;
                mEndAnimationRect.top = newEndValue;
                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
                return;
            } else {
                // no new animation needed, let's just apply the value
                setBackgroundTop(newEndValue);
                return;
            }
        }
        if (previousAnimator != null) {
            previousAnimator.cancel();
        }
        ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundTop",
                mCurrentBounds.top, newEndValue);
        Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
        animator.setInterpolator(interpolator);
        animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
        // remove the tag when the animation is finished
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mStartAnimationRect.top = -1;
                mEndAnimationRect.top = -1;
                mTopAnimator = null;
            }
        });
        animator.start();
        mStartAnimationRect.top = mCurrentBounds.top;
        mEndAnimationRect.top = newEndValue;
        mTopAnimator = animator;
    }

    private void startBottomAnimation(boolean animate) {
        int previousStartValue = mStartAnimationRect.bottom;
        int previousEndValue = mEndAnimationRect.bottom;
        int newEndValue = mBounds.bottom;
        ObjectAnimator previousAnimator = mBottomAnimator;
        if (previousAnimator != null && previousEndValue == newEndValue) {
            return;
        }
        if (!animate) {
            // just a local update was performed
            if (previousAnimator != null) {
                // we need to increase all animation keyframes of the previous animator by the
                // relative change to the end value
                PropertyValuesHolder[] values = previousAnimator.getValues();
                values[0].setIntValues(previousStartValue, newEndValue);
                mStartAnimationRect.bottom = previousStartValue;
                mEndAnimationRect.bottom = newEndValue;
                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
                return;
            } else {
                // no new animation needed, let's just apply the value
                setBackgroundBottom(newEndValue);
                return;
            }
        }
        if (previousAnimator != null) {
            previousAnimator.cancel();
        }
        ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundBottom",
                mCurrentBounds.bottom, newEndValue);
        Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
        animator.setInterpolator(interpolator);
        animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
        // remove the tag when the animation is finished
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mStartAnimationRect.bottom = -1;
                mEndAnimationRect.bottom = -1;
                mBottomAnimator = null;
            }
        });
        animator.start();
        mStartAnimationRect.bottom = mCurrentBounds.bottom;
        mEndAnimationRect.bottom = newEndValue;
        mBottomAnimator = animator;
    }

    private void setBackgroundTop(int top) {
        mCurrentBounds.top = top;
        mOwningView.invalidate();
    }

    private void setBackgroundBottom(int bottom) {
        mCurrentBounds.bottom = bottom;
        mOwningView.invalidate();
    }

    public ExpandableView getFirstVisibleChild() {
        return mFirstVisibleChild;
@@ -215,91 +57,4 @@ public class NotificationSection {
        return changed;
    }

    public void resetCurrentBounds() {
        mCurrentBounds.set(mBounds);
    }

    /**
     * Returns true if {@code top} is equal to the top of this section (if not currently animating)
     * or where the top of this section will be when animation completes.
     */
    public boolean isTargetTop(int top) {
        return (mTopAnimator == null && mCurrentBounds.top == top)
                || (mTopAnimator != null && mEndAnimationRect.top == top);
    }

    /**
     * Returns true if {@code bottom} is equal to the bottom of this section (if not currently
     * animating) or where the bottom of this section will be when animation completes.
     */
    public boolean isTargetBottom(int bottom) {
        return (mBottomAnimator == null && mCurrentBounds.bottom == bottom)
                || (mBottomAnimator != null && mEndAnimationRect.bottom == bottom);
    }

    /**
     * Update the bounds of this section based on it's views
     *
     * @param minTopPosition the minimum position that the top needs to have
     * @param minBottomPosition the minimum position that the bottom needs to have
     * @return the position of the new bottom
     */
    public int updateBounds(int minTopPosition, int minBottomPosition,
            boolean shiftBackgroundWithFirst) {
        int top = minTopPosition;
        int bottom = minTopPosition;
        ExpandableView firstView = getFirstVisibleChild();
        if (firstView != null) {
            // Round Y up to avoid seeing the background during animation
            int finalTranslationY = (int) Math.ceil(ViewState.getFinalTranslationY(firstView));
            // TODO: look into the already animating part
            int newTop;
            if (isTargetTop(finalTranslationY)) {
                // we're ending up at the same location as we are now, let's just skip the
                // animation
                newTop = finalTranslationY;
            } else {
                newTop = (int) Math.ceil(firstView.getTranslationY());
            }
            top = Math.max(newTop, top);
            if (firstView.showingPulsing()) {
                // If we're pulsing, the notification can actually go below!
                bottom = Math.max(bottom, finalTranslationY
                        + ExpandableViewState.getFinalActualHeight(firstView));
                if (shiftBackgroundWithFirst) {
                    mBounds.left += Math.max(firstView.getTranslation(), 0);
                    mBounds.right += Math.min(firstView.getTranslation(), 0);
                }
            }
        }
        ExpandableView lastView = getLastVisibleChild();
        if (lastView != null) {
            float finalTranslationY = ViewState.getFinalTranslationY(lastView);
            int finalHeight = ExpandableViewState.getFinalActualHeight(lastView);
            // Round Y down to avoid seeing the background during animation
            int finalBottom = (int) Math.floor(
                    finalTranslationY + finalHeight - lastView.getClipBottomAmount());
            int newBottom;
            if (isTargetBottom(finalBottom)) {
                // we're ending up at the same location as we are now, lets just skip the animation
                newBottom = finalBottom;
            } else {
                newBottom = (int) (lastView.getTranslationY() + lastView.getActualHeight()
                        - lastView.getClipBottomAmount());
                // The background can never be lower than the end of the last view
                minBottomPosition = (int) Math.min(
                        lastView.getTranslationY() + lastView.getActualHeight(),
                        minBottomPosition);
            }
            bottom = Math.max(bottom, Math.max(newBottom, minBottomPosition));
        }
        bottom = Math.max(top, bottom);
        mBounds.top = top;
        mBounds.bottom = bottom;
        return bottom;
    }

    public boolean needsBackground() {
        return mFirstVisibleChild != null && mBucket != BUCKET_MEDIA_CONTROLS;
    }
}
+63 −56
Original line number Diff line number Diff line
@@ -40,7 +40,9 @@ import javax.inject.Inject
 *
 * TODO: Move remaining sections logic from NSSL into this class.
 */
class NotificationSectionsManager @Inject internal constructor(
class NotificationSectionsManager
@Inject
internal constructor(
    private val configurationController: ConfigurationController,
    private val keyguardMediaController: KeyguardMediaController,
    private val sectionsFeatureManager: NotificationSectionsFeatureManager,
@@ -52,7 +54,8 @@ class NotificationSectionsManager @Inject internal constructor(
    @SilentHeader private val silentHeaderController: SectionHeaderController
) : SectionProvider {

    private val configurationListener = object : ConfigurationController.ConfigurationListener {
    private val configurationListener =
        object : ConfigurationController.ConfigurationListener {
            override fun onLocaleListChanged() {
                reinflateViews()
            }
@@ -91,13 +94,12 @@ class NotificationSectionsManager @Inject internal constructor(
    }

    fun createSectionsForBuckets(): Array<NotificationSection> =
            sectionsFeatureManager.getNotificationBuckets()
                    .map { NotificationSection(parent, it) }
        sectionsFeatureManager
            .getNotificationBuckets()
            .map { NotificationSection(it) }
            .toTypedArray()

    /**
     * Reinflates the entire notification header, including all decoration views.
     */
    /** Reinflates the entire notification header, including all decoration views. */
    fun reinflateViews() {
        silentHeaderController.reinflateView(parent)
        alertingHeaderController.reinflateView(parent)
@@ -115,7 +117,8 @@ class NotificationSectionsManager @Inject internal constructor(
            view === incomingHeaderView ||
            getBucket(view) != getBucket(previous)

    private fun getBucket(view: View?): Int? = when {
    private fun getBucket(view: View?): Int? =
        when {
            view === silentHeaderView -> BUCKET_SILENT
            view === incomingHeaderView -> BUCKET_HEADS_UP
            view === mediaControlsView -> BUCKET_MEDIA_CONTROLS
@@ -127,21 +130,20 @@ class NotificationSectionsManager @Inject internal constructor(

    private sealed class SectionBounds {

        data class Many(
            val first: ExpandableView,
            val last: ExpandableView
        ) : SectionBounds()
        data class Many(val first: ExpandableView, val last: ExpandableView) : SectionBounds()

        data class One(val lone: ExpandableView) : SectionBounds()
        object None : SectionBounds()

        fun addNotif(notif: ExpandableView): SectionBounds = when (this) {
        fun addNotif(notif: ExpandableView): SectionBounds =
            when (this) {
                is None -> One(notif)
                is One -> Many(lone, notif)
                is Many -> copy(last = notif)
            }

        fun updateSection(section: NotificationSection): Boolean = when (this) {
        fun updateSection(section: NotificationSection): Boolean =
            when (this) {
                is None -> section.setFirstAndLastVisibleChildren(null, null)
                is One -> section.setFirstAndLastVisibleChildren(lone, lone)
                is Many -> section.setFirstAndLastVisibleChildren(first, last)
@@ -167,7 +169,9 @@ class NotificationSectionsManager @Inject internal constructor(
        children: List<ExpandableView>
    ): Boolean {
        // Create mapping of bucket to section
        val sectionBounds = children.asSequence()
        val sectionBounds =
            children
                .asSequence()
                // Group children by bucket
                .groupingBy {
                    getBucket(it)
@@ -185,7 +189,8 @@ class NotificationSectionsManager @Inject internal constructor(
        val oldLastChildren = sections.mapNotNull { it.lastVisibleChild }.toSet().toMutableSet()

        // Update each section with the associated boundary, tracking if there was a change
        val changed = sections.fold(false) { changed, section ->
        val changed =
            sections.fold(false) { changed, section ->
                val bounds = sectionBounds[section.bucket] ?: SectionBounds.None
                val isSectionChanged = bounds.updateSection(section)
                isSectionChanged || changed
@@ -229,12 +234,14 @@ class NotificationSectionsManager @Inject internal constructor(
    private fun logSections(sections: Array<NotificationSection>) {
        for (i in sections.indices) {
            val s = sections[i]
            val fs = when (val first = s.firstVisibleChild) {
            val fs =
                when (val first = s.firstVisibleChild) {
                    null -> "(null)"
                    is ExpandableNotificationRow -> first.entry.key
                    else -> Integer.toHexString(System.identityHashCode(first))
                }
            val ls = when (val last = s.lastVisibleChild) {
            val ls =
                when (val last = s.lastVisibleChild) {
                    null -> "(null)"
                    is ExpandableNotificationRow -> last.entry.key
                    else -> Integer.toHexString(System.identityHashCode(last))
+1 −1
Original line number Diff line number Diff line
@@ -2974,7 +2974,7 @@ public class NotificationStackScrollLayout

    private void updateFirstAndLastBackgroundViews() {
        ExpandableView lastChild = getLastChildWithBackground();
        boolean sectionViewsChanged = mSectionsManager.updateFirstAndLastViewsForAllSections(
        mSectionsManager.updateFirstAndLastViewsForAllSections(
                mSections, getChildrenWithBackground());

        mAmbientState.setLastVisibleBackgroundChild(lastChild);
+3 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.statusbar.notification

import android.platform.test.annotations.DisableFlags
import android.provider.DeviceConfig
import android.provider.Settings

@@ -25,6 +26,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito

import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.Utils
import com.android.systemui.util.mockito.any
@@ -41,6 +43,7 @@ import org.mockito.quality.Strictness

@RunWith(AndroidJUnit4::class)
@SmallTest
@DisableFlags(PriorityPeopleSection.FLAG_NAME)  // this class has no logic with the flag enabled
class NotificationSectionsFeatureManagerTest : SysuiTestCase() {
    var manager: NotificationSectionsFeatureManager? = null
    val proxyFake = DeviceConfigProxyFake()