DO NOT MERGE: unrevert "Force FGS notifications to show for a minimum time" am: b0d526ac12
Change-Id: Id030fe62bd5d665e291ce6ac11b03fdaf1f2af88
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.NotificationLifetimeExtender;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
|
||||
/**
|
||||
* 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());
|
||||
|
||||
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)) {
|
||||
mManagedEntries.remove(entry);
|
||||
if (mNotificationSafeToRemoveCallback != null) {
|
||||
mNotificationSafeToRemoveCallback.onSafeToRemove(entry.key);
|
||||
}
|
||||
}
|
||||
};
|
||||
long delayAmt = MIN_FGS_TIME_MS
|
||||
- (System.currentTimeMillis() - entry.notification.getPostTime());
|
||||
mHandler.postDelayed(r, delayAmt);
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,9 @@ public class ForegroundServiceNotificationListener {
|
||||
removeNotification(entry.notification);
|
||||
}
|
||||
});
|
||||
|
||||
notificationEntryManager.addNotificationLifetimeExtender(
|
||||
new ForegroundServiceLifetimeExtender());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,6 +26,18 @@ public interface NotificationLifetimeExtender {
|
||||
*/
|
||||
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
|
||||
* shouldManage is true, this is where the extender starts managing the entry internally and is
|
||||
|
||||
@@ -279,11 +279,25 @@ public class NotificationEntryManager implements
|
||||
}
|
||||
|
||||
final NotificationEntry entry = mNotificationData.get(key);
|
||||
|
||||
abortExistingInflation(key);
|
||||
|
||||
boolean lifetimeExtended = false;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!lifetimeExtended) {
|
||||
abortExistingInflation(key);
|
||||
}
|
||||
|
||||
if (entry != null) {
|
||||
// If a manager needs to keep the notification around for whatever reason, we
|
||||
// keep the notification
|
||||
|
||||
@@ -224,7 +224,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
|
||||
mVisualStabilityManager.setUpWithPresenter(this);
|
||||
mGutsManager.setUpWithPresenter(this,
|
||||
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.
|
||||
// TODO: figure out how to do this correctly once Dependency.get() is gone.
|
||||
Dependency.get(ForegroundServiceNotificationListener.class);
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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 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 ForegroundServiceLifetimeExtenderTest extends SysuiTestCase {
|
||||
private ForegroundServiceLifetimeExtender mExtender = new 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);
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
}
|
||||
@@ -5414,8 +5414,16 @@ public class NotificationManagerService extends SystemService {
|
||||
}
|
||||
|
||||
synchronized (mNotificationLock) {
|
||||
// Look for the notification, searching both the posted and enqueued lists.
|
||||
NotificationRecord r = findNotificationLocked(mPkg, mTag, mId, mUserId);
|
||||
// If the notification is currently enqueued, repost this runnable so it has a
|
||||
// chance to notify listeners
|
||||
if ((findNotificationByListLocked(mEnqueuedNotifications, mPkg, mTag, mId, mUserId))
|
||||
!= null) {
|
||||
mHandler.post(this);
|
||||
return;
|
||||
}
|
||||
// Look for the notification in the posted list, since we already checked enqueued.
|
||||
NotificationRecord r =
|
||||
findNotificationByListLocked(mNotificationList, mPkg, mTag, mId, mUserId);
|
||||
if (r != null) {
|
||||
// The notification was found, check if it should be removed.
|
||||
|
||||
|
||||
@@ -1016,6 +1016,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
|
||||
assertEquals(0, mService.getNotificationRecordCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelImmediatelyAfterEnqueueNotifiesListeners_ForegroundServiceFlag()
|
||||
throws Exception {
|
||||
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
|
||||
sbn.getNotification().flags =
|
||||
Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE;
|
||||
mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
|
||||
sbn.getId(), sbn.getNotification(), sbn.getUserId());
|
||||
mBinderService.cancelNotificationWithTag(PKG, "tag", sbn.getId(), sbn.getUserId());
|
||||
waitForIdle();
|
||||
verify(mListeners, times(1)).notifyPostedLocked(any(), any());
|
||||
verify(mListeners, times(1)).notifyRemovedLocked(any(), anyInt(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserInitiatedClearAll_noLeak() throws Exception {
|
||||
final NotificationRecord n = generateNotificationRecord(
|
||||
|
||||
Reference in New Issue
Block a user