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

Commit df6c9291 authored by Martijn Coenen's avatar Martijn Coenen
Browse files

Revert "Manual revert of 5s FGS notification enforcement"

This reverts commit b53036a6.

Reason for revert: Testing for b/175673373

Change-Id: I6b142ce6a7174faf343996e2d92a29bf5a0965ca
parent b53036a6
Loading
Loading
Loading
Loading
+109 −0
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);
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ public class ForegroundServiceNotificationListener {
            ForegroundServiceController foregroundServiceController,
            NotificationEntryManager notificationEntryManager,
            NotifPipeline notifPipeline,
            ForegroundServiceLifetimeExtender fgsLifetimeExtender,
            SystemClock systemClock) {
        mContext = context;
        mForegroundServiceController = foregroundServiceController;
@@ -77,6 +78,7 @@ public class ForegroundServiceNotificationListener {
                removeNotification(entry.getSbn());
            }
        });
        mEntryManager.addNotificationLifetimeExtender(fgsLifetimeExtender);

        notifPipeline.addCollectionListener(new NotifCollectionListener() {
            @Override
+65 −0
Original line number Diff line number Diff line
@@ -30,8 +30,12 @@ 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;

/**
@@ -71,6 +75,9 @@ 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);

@@ -111,6 +118,64 @@ 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.
     */
+7 −1
Original line number Diff line number Diff line
@@ -24,7 +24,10 @@ 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;

@@ -51,6 +54,8 @@ 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;
@@ -79,7 +84,8 @@ public class ForegroundServiceControllerTest extends SysuiTestCase {
        MockitoAnnotations.initMocks(this);
        mFsc = new ForegroundServiceController(mAppOpsController, mMainHandler);
        mListener = new ForegroundServiceNotificationListener(
                mContext, mFsc, mEntryManager, mNotifPipeline, mClock);
                mContext, mFsc, mEntryManager, mNotifPipeline,
                mock(ForegroundServiceLifetimeExtender.class), mClock);
        ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
                ArgumentCaptor.forClass(NotificationEntryListener.class);
        verify(mEntryManager).addNotificationEntryListener(
+110 −0
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