blob: 407e1e671f8615540a00a4861e290205be28acd1 [file] [log] [blame]
/*
* 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.statusbar.notification.collection.coordinator;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArraySet;
import androidx.test.filters.SmallTest;
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.List;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class ForegroundCoordinatorTest extends SysuiTestCase {
private static final String TEST_PKG = "test_pkg";
private static final int NOTIF_USER_ID = 0;
@Mock private ForegroundServiceController mForegroundServiceController;
@Mock private AppOpsController mAppOpsController;
@Mock private NotifPipeline mNotifPipeline;
@Captor private ArgumentCaptor<AppOpsController.Callback> mAppOpsCaptor;
private NotificationEntry mEntry;
private Notification mNotification;
private ForegroundCoordinator mForegroundCoordinator;
private NotifFilter mForegroundFilter;
private AppOpsController.Callback mAppOpsCallback;
private NotifLifetimeExtender mForegroundNotifLifetimeExtender;
private FakeSystemClock mClock = new FakeSystemClock();
private FakeExecutor mExecutor = new FakeExecutor(mClock);
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
allowTestableLooperAsMainThread();
mForegroundCoordinator =
new ForegroundCoordinator(
mForegroundServiceController,
mAppOpsController,
mExecutor);
mNotification = new Notification();
mEntry = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID))
.setNotification(mNotification)
.build();
ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
ArgumentCaptor<NotifLifetimeExtender> lifetimeExtenderCaptor =
ArgumentCaptor.forClass(NotifLifetimeExtender.class);
mForegroundCoordinator.attach(mNotifPipeline);
verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture());
verify(mNotifPipeline, times(1)).addNotificationLifetimeExtender(
lifetimeExtenderCaptor.capture());
verify(mAppOpsController).addCallback(any(int[].class), mAppOpsCaptor.capture());
mForegroundFilter = filterCaptor.getValue();
mForegroundNotifLifetimeExtender = lifetimeExtenderCaptor.getValue();
mAppOpsCallback = mAppOpsCaptor.getValue();
}
@Test
public void filterTest_disclosureUnnecessary() {
StatusBarNotification sbn = mEntry.getSbn();
// GIVEN the notification is a disclosure notification
when(mForegroundServiceController.isDisclosureNotification(sbn)).thenReturn(true);
// GIVEN the disclosure isn't needed for this user
when(mForegroundServiceController.isDisclosureNeededForUser(sbn.getUserId()))
.thenReturn(false);
// THEN filter out the notification
assertTrue(mForegroundFilter.shouldFilterOut(mEntry, 0));
}
@Test
public void filterTest_systemAlertNotificationUnnecessary() {
StatusBarNotification sbn = mEntry.getSbn();
// GIVEN the notification is a system alert notification + not a disclosure notification
when(mForegroundServiceController.isSystemAlertNotification(sbn)).thenReturn(true);
when(mForegroundServiceController.isDisclosureNotification(sbn)).thenReturn(false);
// GIVEN the alert notification isn't needed for this user
final Bundle extras = new Bundle();
extras.putStringArray(Notification.EXTRA_FOREGROUND_APPS,
new String[]{TEST_PKG});
mNotification.extras = extras;
when(mForegroundServiceController.isSystemAlertWarningNeeded(sbn.getUserId(), TEST_PKG))
.thenReturn(false);
// THEN filter out the notification
assertTrue(mForegroundFilter.shouldFilterOut(mEntry, 0));
}
@Test
public void filterTest_doNotFilter() {
StatusBarNotification sbn = mEntry.getSbn();
// GIVEN the notification isn't a system alert notification nor a disclosure notification
when(mForegroundServiceController.isSystemAlertNotification(sbn)).thenReturn(false);
when(mForegroundServiceController.isDisclosureNotification(sbn)).thenReturn(false);
// THEN don't filter out the notification
assertFalse(mForegroundFilter.shouldFilterOut(mEntry, 0));
}
@Test
public void extendLifetimeText_notForeground() {
// GIVEN the notification doesn't represent a foreground service
mNotification.flags = 0;
// THEN don't extend the lifetime
assertFalse(mForegroundNotifLifetimeExtender
.shouldExtendLifetime(mEntry, NotificationListenerService.REASON_CLICK));
}
@Test
public void extendLifetimeText_foregroundNotifRecentlyPosted() {
// GIVEN the notification represents a foreground service that was just posted
mNotification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
mEntry = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID))
.setSbn(new StatusBarNotification(TEST_PKG, TEST_PKG, NOTIF_USER_ID, "",
NOTIF_USER_ID, NOTIF_USER_ID, mNotification,
new UserHandle(NOTIF_USER_ID), "", System.currentTimeMillis()))
.setNotification(mNotification)
.build();
// THEN extend the lifetime
assertTrue(mForegroundNotifLifetimeExtender
.shouldExtendLifetime(mEntry, NotificationListenerService.REASON_CLICK));
}
@Test
public void extendLifetimeText_foregroundNotifOld() {
// GIVEN the notification represents a foreground service that was posted 10 seconds ago
mNotification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
mEntry = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID))
.setSbn(new StatusBarNotification(TEST_PKG, TEST_PKG, NOTIF_USER_ID, "",
NOTIF_USER_ID, NOTIF_USER_ID, mNotification,
new UserHandle(NOTIF_USER_ID), "",
System.currentTimeMillis() - 10000))
.setNotification(mNotification)
.build();
// THEN don't extend the lifetime because the extended time exceeds
// ForegroundCoordinator.MIN_FGS_TIME_MS
assertFalse(mForegroundNotifLifetimeExtender
.shouldExtendLifetime(mEntry, NotificationListenerService.REASON_CLICK));
}
@Test
public void testAppOpsAreApplied() {
// GIVEN Three current notifications, two with the same key but from different users
NotificationEntry entry1 = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID))
.setPkg(TEST_PKG)
.setId(1)
.build();
NotificationEntry entry2 = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID))
.setPkg(TEST_PKG)
.setId(2)
.build();
NotificationEntry entry2Other = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID + 1))
.setPkg(TEST_PKG)
.setId(2)
.build();
when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry1, entry2, entry2Other));
// GIVEN that entry2 is currently associated with a foreground service
when(mForegroundServiceController.getStandardLayoutKey(0, TEST_PKG))
.thenReturn(entry2.getKey());
// WHEN a new app ops code comes in
mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, true);
mExecutor.runAllReady();
// THEN entry2's app ops are updated, but no one else's are
assertEquals(
new ArraySet<>(),
entry1.mActiveAppOps);
assertEquals(
new ArraySet<>(List.of(47)),
entry2.mActiveAppOps);
assertEquals(
new ArraySet<>(),
entry2Other.mActiveAppOps);
}
@Test
public void testAppOpsAreRemoved() {
// GIVEN One notification which is associated with app ops
NotificationEntry entry = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID))
.setPkg(TEST_PKG)
.setId(2)
.build();
when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry));
when(mForegroundServiceController.getStandardLayoutKey(0, TEST_PKG))
.thenReturn(entry.getKey());
// GIVEN that the notification's app ops are already [47, 33]
mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, true);
mAppOpsCallback.onActiveStateChanged(33, NOTIF_USER_ID, TEST_PKG, true);
mExecutor.runAllReady();
assertEquals(
new ArraySet<>(List.of(47, 33)),
entry.mActiveAppOps);
// WHEN one of the app ops is removed
mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, false);
mExecutor.runAllReady();
// THEN the entry's active app ops are updated as well
assertEquals(
new ArraySet<>(List.of(33)),
entry.mActiveAppOps);
}
}