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

Commit 58c8c22f authored by Steve Elliott's avatar Steve Elliott
Browse files

Allow moving notifs when user changes bundle setting

Flag: com.android.systemui.notification_bundle_ui
Fixes: 422348323
Test: manual
Change-Id: I0fdd375b2fcc791d5c0e89f0534c993a665fdb65
parent 2726872f
Loading
Loading
Loading
Loading
+3 −4
Original line number Diff line number Diff line
@@ -591,9 +591,8 @@ class NotificationEntryAdapterTest : SysuiTestCase() {
        underTest = factory.create(entry) as NotificationEntryAdapter

        underTest.onBundleDisabled()
        assertThat(underTest.isMarkedForUserTriggeredMovement).isTrue()
        verify(kosmos.mockVisualStabilityCoordinator)
            .temporarilyAllowSectionChanges(eq(entry), anyLong())
            .temporarilyAllowFreeMovement(eq(entry), anyLong())
    }

    @Test
@@ -630,8 +629,8 @@ class NotificationEntryAdapterTest : SysuiTestCase() {
        underTest = factory.create(summaryEntry) as NotificationEntryAdapter
        underTest.onBundleDisabled()
        verify(kosmos.mockVisualStabilityCoordinator)
            .temporarilyAllowSectionChanges(eq(summaryEntry), anyLong())
            .temporarilyAllowFreeMovement(eq(summaryEntry), anyLong())
        verify(kosmos.mockVisualStabilityCoordinator)
            .temporarilyAllowSectionChanges(eq(childEntry), anyLong())
            .temporarilyAllowFreeMovement(eq(childEntry), anyLong())
    }
}
+1 −3
Original line number Diff line number Diff line
@@ -291,9 +291,7 @@ class NotificationEntryAdapter(
    }

    override fun onBundleDisabled() {
        markForUserTriggeredMovement(true)
        onImportanceChanged()

        visualStabilityCoordinator.temporarilyAllowFreeMovement(entry, SystemClock.uptimeMillis())
        if (isGroupRoot()) {
            row.attachedChildren?.forEach { it.entryAdapter.onBundleDisabled() }
        }
+66 −4
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import com.android.systemui.Dumpable;
@@ -47,6 +48,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository;
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.shared.NotificationMinimalism;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -109,6 +111,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
    // value: runnable that when run removes its associated RemoveOverrideSuppressionRunnable
    // from the DelayableExecutor's queue
    private Map<String, Runnable> mEntriesThatCanChangeSection = new HashMap<>();
    private Map<String, Runnable> mEntriesThatCanMoveFreely = new HashMap<>();

    @VisibleForTesting
    protected static final long ALLOW_SECTION_CHANGE_TIMEOUT = 500;
@@ -324,10 +327,12 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
                        isGroupChangeAllowedForEntry =
                                isEveryChangeAllowed()
                                        || canReorderNotificationEntry(entry)
                                        || canMoveForHeadsUp(entry);
                                        || canMoveForHeadsUp(entry)
                                        || canFreelyMoveEntry(entry);
                    } else {
                        isGroupChangeAllowedForEntry = mReorderingAllowed
                                || canMoveForHeadsUp(entry);
                                || canMoveForHeadsUp(entry)
                                || canFreelyMoveEntry(entry);
                    }
                    mIsSuppressingParentChange |= !isGroupChangeAllowedForEntry;
                    return isGroupChangeAllowedForEntry;
@@ -335,7 +340,8 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {

                @Override
                public boolean isParentChangeAllowed(@NonNull GroupEntry entry) {
                    final boolean isBundleChangeAllowedForGroup = isEveryChangeAllowed();
                    final boolean isBundleChangeAllowedForGroup = isEveryChangeAllowed()
                            || canFreelyMoveEntry(entry);
                    mIsSuppressingParentChange |= !isBundleChangeAllowedForGroup;
                    return isBundleChangeAllowedForGroup;
                }
@@ -387,7 +393,9 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
                        }

                        return canReorderNotificationEntry(notificationEntry)
                                || canMoveForHeadsUp(notificationEntry);
                                || canMoveForHeadsUp(notificationEntry)
                                || (notificationEntry != null
                                        && canFreelyMoveEntry(notificationEntry));
                    } else {
                        return mReorderingAllowed || canMoveForHeadsUp(notificationEntry);
                    }
@@ -497,6 +505,41 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
        }
    }

    /**
     * Allows this notification entry to be re-ordered and re-parented in the notification list
     * temporarily until the timeout has passed.
     *
     * Typically this is allowed because the user has directly changed something about the
     * notification and we are reordering based on the user's change.
     *
     * @param entry notification entry that can move freely even if it would be otherwise suppressed
     * @param now current time SystemClock.elapsedRealtime
     */
    public void temporarilyAllowFreeMovement(@NonNull NotificationEntry entry, long now) {
        if (NotificationBundleUi.isUnexpectedlyInLegacyMode()) {
            return;
        }
        final String entryKey = entry.getKey();
        final Runnable existing = mEntriesThatCanMoveFreely.get(entryKey);
        final boolean wasAllowedToMoveFreely = existing != null;

        // If it exists, cancel previous timeout
        if (wasAllowedToMoveFreely) {
            existing.run();
        }

        // Schedule & store new timeout cancellable
        mEntriesThatCanMoveFreely.put(
                entryKey,
                mDelayableExecutor.executeAtTime(
                        () -> mEntriesThatCanMoveFreely.remove(entryKey),
                        now + ALLOW_SECTION_CHANGE_TIMEOUT));

        if (!wasAllowedToMoveFreely) {
            mNotifStabilityManager.invalidateList("temporarilyAllowFreeMovement");
        }
    }

    /**
     * Allows this notification entry to be re-ordered in the notification list temporarily until
     * the timeout has passed.
@@ -529,6 +572,25 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
        }
    }

    private boolean canFreelyMoveEntry(@NonNull GroupEntry entry) {
        if (!NotificationBundleUi.isEnabled()) {
            return false;
        }
        @Nullable NotificationEntry representativeEntry = entry.getRepresentativeEntry();
        if (representativeEntry == null) {
            return false;
        }
        return canFreelyMoveEntry(representativeEntry);
    }

    private boolean canFreelyMoveEntry(@NonNull NotificationEntry entry) {
        if (NotificationBundleUi.isEnabled()) {
            return mEntriesThatCanMoveFreely.containsKey(entry.getKey());
        } else {
            return false;
        }
    }

    final StatusBarStateController.StateListener mStatusBarStateControllerListener =
            new StatusBarStateController.StateListener() {
                @Override
+10 −40
Original line number Diff line number Diff line
@@ -15,34 +15,17 @@
 */
package com.android.systemui.statusbar.notification.row

import android.app.INotificationManager
import android.app.NotificationChannel.NEWS_ID
import android.app.NotificationChannel.PROMOTIONS_ID
import android.app.NotificationChannel.RECS_ID
import android.app.NotificationChannel.SOCIAL_MEDIA_ID
import android.content.Context
import android.content.pm.PackageManager
import android.os.RemoteException
import android.service.notification.Adjustment
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.UiEventLogger
import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.AssistantFeedbackController
import com.android.systemui.statusbar.notification.collection.EntryAdapter
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor
import com.android.systemui.statusbar.notification.row.icon.AppIconProvider
import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import com.google.android.material.materialswitch.MaterialSwitch


/**
 * The guts of a notification revealed when performing a long press, specifically for notifications
 * that are bundled. Contains controls to allow user to disable the feature for the app that posted
@@ -63,20 +46,16 @@ class BundledNotificationInfo(context: Context?, attrs: AttributeSet?) :
        toggle.setOnCheckedChangeListener { buttonView, isChecked ->
            val isAChange = isChecked != enabled
            val done = findViewById<TextView>(R.id.done)
            done.setText(
                if (isAChange)
                    R.string.inline_ok_button
                else
                    R.string.inline_done_button
            )
            done.setText(if (isAChange) R.string.inline_ok_button else R.string.inline_done_button)
        }

        val done = findViewById<TextView>(R.id.done)
        done.setOnClickListener {
            try {
                if (NotificationBundleUi.isEnabled) {
                    mEntryAdapter.markForUserTriggeredMovement(true)
                    mEntryAdapter.onImportanceChanged()
                    if (enabled && !toggle.isChecked) {
                        mEntryAdapter.onBundleDisabled()
                    }
                } else {
                    mEntry.markForUserTriggeredMovement(true)
                    mOnUserInteractionCallback.onImportanceChanged(mEntry)
@@ -92,27 +71,18 @@ class BundledNotificationInfo(context: Context?, attrs: AttributeSet?) :
                throw RuntimeException(e)
            }
        }
        done.setText(
            if (enabled)
                R.string.inline_done_button
            else
                R.string.inline_ok_button
        )
        done.setText(if (enabled) R.string.inline_done_button else R.string.inline_ok_button)
        done.setAccessibilityDelegate(mGutsContainer.accessibilityDelegate)
        val toggleWrapper = findViewById<ViewGroup>(R.id.classification_toggle)
        toggleWrapper.setOnClickListener {
            toggle.performClick()
        }
        toggleWrapper.setOnClickListener { toggle.performClick() }

        findViewById<TextView>(R.id.feature_summary).setText(
            resources.getString(R.string.notification_guts_bundle_summary, mAppName));
        findViewById<TextView>(R.id.feature_summary)
            .setText(resources.getString(R.string.notification_guts_bundle_summary, mAppName))

        val dismissButton = findViewById<View>(R.id.inline_dismiss)
        dismissButton.setOnClickListener(mOnCloseClickListener)
        dismissButton.visibility = if (dismissButton.hasOnClickListeners() && mIsDismissable)
            VISIBLE
        else
            GONE
        dismissButton.visibility =
            if (dismissButton.hasOnClickListeners() && mIsDismissable) VISIBLE else GONE
    }

    companion object {