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

Commit c02e27fc authored by Evan Laird's avatar Evan Laird
Browse files

Revert "Revert "Manual revert of 5s FGS notification enforcement""

This reverts commit df6c9291.

Bug: 171513045
Test: atest SystemUITests
Test: atest StatsdNotificationAtomTest
Change-Id: I236a8a7edd4235319e064aba0872a3723980325c
parent 09cefc02
Loading
Loading
Loading
Loading
+0 −109
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui;

import android.annotation.NonNull;
import android.app.Notification;
import android.os.Handler;
import android.os.Looper;
import android.util.ArraySet;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.statusbar.NotificationInteractionTracker;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.util.time.SystemClock;

import javax.inject.Inject;

/**
 * Extends the lifetime of foreground notification services such that they show for at least
 * five seconds
 */
public class ForegroundServiceLifetimeExtender implements NotificationLifetimeExtender {

    private static final String TAG = "FGSLifetimeExtender";
    @VisibleForTesting
    static final int MIN_FGS_TIME_MS = 5000;

    private NotificationSafeToRemoveCallback mNotificationSafeToRemoveCallback;
    private ArraySet<NotificationEntry> mManagedEntries = new ArraySet<>();
    private Handler mHandler = new Handler(Looper.getMainLooper());
    private final SystemClock mSystemClock;
    private final NotificationInteractionTracker mInteractionTracker;

    @Inject
    public ForegroundServiceLifetimeExtender(
            NotificationInteractionTracker interactionTracker,
            SystemClock systemClock) {
        mSystemClock = systemClock;
        mInteractionTracker = interactionTracker;
    }

    @Override
    public void setCallback(@NonNull NotificationSafeToRemoveCallback callback) {
        mNotificationSafeToRemoveCallback = callback;
    }

    @Override
    public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
        if ((entry.getSbn().getNotification().flags
                & Notification.FLAG_FOREGROUND_SERVICE) == 0) {
            return false;
        }

        // Entry has triggered a HUN or some other interruption, therefore it has been seen and the
        // interrupter might be retaining it anyway.
        if (entry.hasInterrupted()) {
            return false;
        }

        boolean hasInteracted = mInteractionTracker.hasUserInteractedWith(entry.getKey());
        long aliveTime = mSystemClock.uptimeMillis() - entry.getCreationTime();
        return aliveTime < MIN_FGS_TIME_MS && !hasInteracted;
    }

    @Override
    public boolean shouldExtendLifetimeForPendingNotification(
            @NonNull NotificationEntry entry) {
        return shouldExtendLifetime(entry);
    }

    @Override
    public void setShouldManageLifetime(
            @NonNull NotificationEntry entry, boolean shouldManage) {
        if (!shouldManage) {
            mManagedEntries.remove(entry);
            return;
        }

        mManagedEntries.add(entry);

        Runnable r = () -> {
            if (mManagedEntries.contains(entry)) {
                mManagedEntries.remove(entry);
                if (mNotificationSafeToRemoveCallback != null) {
                    mNotificationSafeToRemoveCallback.onSafeToRemove(entry.getKey());
                }
            }
        };
        long delayAmt = MIN_FGS_TIME_MS
                - (mSystemClock.uptimeMillis() - entry.getCreationTime());
        mHandler.postDelayed(r, delayAmt);
    }
}
+0 −2
Original line number Diff line number Diff line
@@ -50,7 +50,6 @@ public class ForegroundServiceNotificationListener {
            ForegroundServiceController foregroundServiceController,
            NotificationEntryManager notificationEntryManager,
            NotifPipeline notifPipeline,
            ForegroundServiceLifetimeExtender fgsLifetimeExtender,
            SystemClock systemClock) {
        mContext = context;
        mForegroundServiceController = foregroundServiceController;
@@ -78,7 +77,6 @@ public class ForegroundServiceNotificationListener {
                removeNotification(entry.getSbn());
            }
        });
        mEntryManager.addNotificationLifetimeExtender(fgsLifetimeExtender);

        notifPipeline.addCollectionListener(new NotifCollectionListener() {
            @Override
+0 −65
Original line number Diff line number Diff line
@@ -30,12 +30,8 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import com.android.systemui.util.concurrency.DelayableExecutor;

import java.util.HashMap;
import java.util.Map;

import javax.inject.Inject;

/**
@@ -75,9 +71,6 @@ public class AppOpsCoordinator implements Coordinator {
    public void attach(NotifPipeline pipeline) {
        mNotifPipeline = pipeline;

        // extend the lifetime of foreground notification services to show for at least 5 seconds
        mNotifPipeline.addNotificationLifetimeExtender(mForegroundLifetimeExtender);

        // filter out foreground service notifications that aren't necessary anymore
        mNotifPipeline.addPreGroupFilter(mNotifFilter);

@@ -118,64 +111,6 @@ public class AppOpsCoordinator implements Coordinator {
        }
    };

    /**
     * Extends the lifetime of foreground notification services such that they show for at least
     * five seconds
     */
    private final NotifLifetimeExtender mForegroundLifetimeExtender =
            new NotifLifetimeExtender() {
        private static final int MIN_FGS_TIME_MS = 5000;
        private OnEndLifetimeExtensionCallback mEndCallback;
        private Map<NotificationEntry, Runnable> mEndRunnables = new HashMap<>();

        @Override
        public String getName() {
            return TAG;
        }

        @Override
        public void setCallback(OnEndLifetimeExtensionCallback callback) {
            mEndCallback = callback;
        }

        @Override
        public boolean shouldExtendLifetime(NotificationEntry entry, int reason) {
            if ((entry.getSbn().getNotification().flags
                    & Notification.FLAG_FOREGROUND_SERVICE) == 0) {
                return false;
            }

            final long currTime = System.currentTimeMillis();
            final boolean extendLife = currTime - entry.getSbn().getPostTime() < MIN_FGS_TIME_MS;

            if (extendLife) {
                if (!mEndRunnables.containsKey(entry)) {
                    final Runnable endExtensionRunnable = () -> {
                        mEndRunnables.remove(entry);
                        mEndCallback.onEndLifetimeExtension(
                                mForegroundLifetimeExtender,
                                entry);
                    };

                    final Runnable cancelRunnable = mMainExecutor.executeDelayed(
                            endExtensionRunnable,
                            MIN_FGS_TIME_MS - (currTime - entry.getSbn().getPostTime()));
                    mEndRunnables.put(entry, cancelRunnable);
                }
            }

            return extendLife;
        }

        @Override
        public void cancelLifetimeExtension(NotificationEntry entry) {
            Runnable cancelRunnable = mEndRunnables.remove(entry);
            if (cancelRunnable != null) {
                cancelRunnable.run();
            }
        }
    };

    /**
     * Puts foreground service notifications into its own section.
     */
+1 −7
Original line number Diff line number Diff line
@@ -24,10 +24,7 @@ import static junit.framework.TestCase.fail;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@@ -54,8 +51,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.util.time.FakeSystemClock;

import junit.framework.Assert;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -84,8 +79,7 @@ public class ForegroundServiceControllerTest extends SysuiTestCase {
        MockitoAnnotations.initMocks(this);
        mFsc = new ForegroundServiceController(mAppOpsController, mMainHandler);
        mListener = new ForegroundServiceNotificationListener(
                mContext, mFsc, mEntryManager, mNotifPipeline,
                mock(ForegroundServiceLifetimeExtender.class), mClock);
                mContext, mFsc, mEntryManager, mNotifPipeline, mClock);
        ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
                ArgumentCaptor.forClass(NotificationEntryListener.class);
        verify(mEntryManager).addNotificationEntryListener(
+0 −110
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui;

import static com.android.systemui.ForegroundServiceLifetimeExtender.MIN_FGS_TIME_MS;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import android.app.Notification;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.systemui.statusbar.NotificationInteractionTracker;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.util.time.FakeSystemClock;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@RunWith(AndroidJUnit4.class)
@SmallTest
public class ForegroundServiceNotificationListenerTest extends SysuiTestCase {
    private ForegroundServiceLifetimeExtender mExtender;
    private NotificationEntry mEntry;
    private Notification mNotif;
    private final FakeSystemClock mClock = new FakeSystemClock();

    @Mock
    private NotificationInteractionTracker mInteractionTracker;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mExtender = new ForegroundServiceLifetimeExtender(mInteractionTracker, mClock);

        mNotif = new Notification.Builder(mContext, "")
                .setSmallIcon(R.drawable.ic_person)
                .setContentTitle("Title")
                .setContentText("Text")
                .build();

        mEntry = new NotificationEntryBuilder()
                .setCreationTime(mClock.uptimeMillis())
                .setNotification(mNotif)
                .build();
    }

    /**
     * ForegroundServiceLifetimeExtenderTest
     */
    @Test
    public void testShouldExtendLifetime_should_foreground() {
        // Extend the lifetime of a FGS notification iff it has not been visible
        // for the minimum time
        mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE;

        // No time has elapsed, keep showing
        assertTrue(mExtender.shouldExtendLifetime(mEntry));
    }

    @Test
    public void testShouldExtendLifetime_shouldNot_foreground() {
        mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE;

        // Entry was created at mClock.uptimeMillis(), advance it MIN_FGS_TIME_MS + 1
        mClock.advanceTime(MIN_FGS_TIME_MS + 1);
        assertFalse(mExtender.shouldExtendLifetime(mEntry));
    }

    @Test
    public void testShouldExtendLifetime_shouldNot_notForeground() {
        mNotif.flags = 0;

        // Entry was created at mClock.uptimeMillis(), advance it MIN_FGS_TIME_MS + 1
        mClock.advanceTime(MIN_FGS_TIME_MS + 1);
        assertFalse(mExtender.shouldExtendLifetime(mEntry));
    }

    @Test
    public void testShouldExtendLifetime_shouldNot_interruped() {
        // GIVEN a notification that would trigger lifetime extension
        mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE;

        // GIVEN the notification has alerted
        mEntry.setInterruption();

        // THEN the notification does not need to have its lifetime extended by this extender
        assertFalse(mExtender.shouldExtendLifetime(mEntry));
    }
}
Loading