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

Commit 03a69951 authored by Evan Laird's avatar Evan Laird Committed by android-build-merger
Browse files

Merge "Force FGS notifications to show for a minimum time" into qt-qpr1-dev

am: 4514a4e4

Change-Id: I1221fa190ca7409bfab021eb56b37df2c436dfe6
parents a3b9c46f 4514a4e4
Loading
Loading
Loading
Loading
+70 −0
Original line number Original line Diff line number Diff line
@@ -16,14 +16,20 @@


package com.android.systemui;
package com.android.systemui;


import android.annotation.NonNull;
import android.app.Notification;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Context;
import android.os.Bundle;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.service.notification.StatusBarNotification;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.Log;
import android.util.Log;


import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -66,6 +72,9 @@ public class ForegroundServiceNotificationListener {
                removeNotification(entry.notification);
                removeNotification(entry.notification);
            }
            }
        });
        });

        notificationEntryManager.addNotificationLifetimeExtender(
                new ForegroundServiceNotificationListener.ForegroundServiceLifetimeExtender());
    }
    }


    /**
    /**
@@ -144,4 +153,65 @@ public class ForegroundServiceNotificationListener {
                },
                },
                true /* create if not found */);
                true /* create if not found */);
    }
    }

    /**
     * Extends the lifetime of foreground notification services such that they show for at least
     * five seconds
     */
    public static 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());

        public ForegroundServiceLifetimeExtender() {
        }

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

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

            long currentTime = System.currentTimeMillis();
            return currentTime - entry.notification.getPostTime() < MIN_FGS_TIME_MS;
        }

        @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)) {
                    if (mNotificationSafeToRemoveCallback != null) {
                        mNotificationSafeToRemoveCallback.onSafeToRemove(entry.key);
                    }
                    mManagedEntries.remove(entry);
                }
            };
            mHandler.postDelayed(r, MIN_FGS_TIME_MS);
        }
    }
}
}
+12 −0
Original line number Original line Diff line number Diff line
@@ -26,6 +26,18 @@ public interface NotificationLifetimeExtender {
     */
     */
    boolean shouldExtendLifetime(@NonNull NotificationEntry entry);
    boolean shouldExtendLifetime(@NonNull NotificationEntry entry);


    /**
     * It's possible that a notification was canceled before it ever became visible. This callback
     * gives lifetime extenders a chance to make sure it shows up. For example if a foreground
     * service is canceled too quickly but we still want to make sure a FGS notification shows.
     * @param pendingEntry the canceled (but pending) entry
     * @return true if the notification lifetime should be extended
     */
    default boolean shouldExtendLifetimeForPendingNotification(
            @NonNull NotificationEntry pendingEntry) {
        return false;
    }

    /**
    /**
     * Sets whether or not the lifetime should be managed by the extender.  In practice, if
     * Sets whether or not the lifetime should be managed by the extender.  In practice, if
     * shouldManage is true, this is where the extender starts managing the entry internally and is
     * shouldManage is true, this is where the extender starts managing the entry internally and is
+16 −2
Original line number Original line Diff line number Diff line
@@ -281,10 +281,24 @@ public class NotificationEntryManager implements
        }
        }


        final NotificationEntry entry = mNotificationData.get(key);
        final NotificationEntry entry = mNotificationData.get(key);
        boolean lifetimeExtended = false;


        abortExistingInflation(key);
        // Notification was canceled before it got inflated
        if (entry == null) {
            NotificationEntry pendingEntry = mPendingNotifications.get(key);
            if (pendingEntry != null) {
                for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) {
                    if (extender.shouldExtendLifetimeForPendingNotification(pendingEntry)) {
                        extendLifetime(pendingEntry, extender);
                        lifetimeExtended = true;
                    }
                }
            }
        }


        boolean lifetimeExtended = false;
        if (!lifetimeExtended) {
            abortExistingInflation(key);
        }


        if (entry != null) {
        if (entry != null) {
            // If a manager needs to keep the notification around for whatever reason, we
            // If a manager needs to keep the notification around for whatever reason, we
+1 −1
Original line number Original line Diff line number Diff line
@@ -224,7 +224,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
            mVisualStabilityManager.setUpWithPresenter(this);
            mVisualStabilityManager.setUpWithPresenter(this);
            mGutsManager.setUpWithPresenter(this,
            mGutsManager.setUpWithPresenter(this,
                    notifListContainer, mCheckSaveListener, mOnSettingsClickListener);
                    notifListContainer, mCheckSaveListener, mOnSettingsClickListener);
            // ForegroundServiceControllerListener adds its listener in its constructor
            // ForegroundServiceNotificationListener adds its listener in its constructor
            // but we need to request it here in order for it to be instantiated.
            // but we need to request it here in order for it to be instantiated.
            // TODO: figure out how to do this correctly once Dependency.get() is gone.
            // TODO: figure out how to do this correctly once Dependency.get() is gone.
            Dependency.get(ForegroundServiceNotificationListener.class);
            Dependency.get(ForegroundServiceNotificationListener.class);
+86 −0
Original line number Original line 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.ForegroundServiceNotificationListener.ForegroundServiceLifetimeExtender.MIN_FGS_TIME_MS;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import android.app.Notification;
import android.service.notification.StatusBarNotification;

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

import com.android.systemui.statusbar.notification.collection.NotificationEntry;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
@SmallTest
public class ForegroundServiceNotificationListenerTest extends SysuiTestCase {
    private ForegroundServiceNotificationListener.ForegroundServiceLifetimeExtender mExtender =
            new ForegroundServiceNotificationListener.ForegroundServiceLifetimeExtender();
    private StatusBarNotification mSbn;
    private NotificationEntry mEntry;
    private Notification mNotif;

    @Before
    public void setup() {
        mNotif = new Notification.Builder(mContext, "")
                .setSmallIcon(R.drawable.ic_person)
                .setContentTitle("Title")
                .setContentText("Text")
                .build();

        mSbn = mock(StatusBarNotification.class);
        when(mSbn.getNotification()).thenReturn(mNotif);

        mEntry = new NotificationEntry(mSbn);
    }

    /**
     * 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;
        when(mSbn.getPostTime()).thenReturn(System.currentTimeMillis());
        assertTrue(mExtender.shouldExtendLifetime(mEntry));
    }

    @Test
    public void testShouldExtendLifetime_shouldNot_foreground() {
        mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE;
        when(mSbn.getPostTime()).thenReturn(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1);
        assertFalse(mExtender.shouldExtendLifetime(mEntry));
    }

    @Test
    public void testShouldExtendLifetime_shouldNot_notForeground() {
        mNotif.flags = 0;
        when(mSbn.getPostTime()).thenReturn(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1);
        assertFalse(mExtender.shouldExtendLifetime(mEntry));
    }
}
Loading