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

Commit 1aec45fe authored by Alexander Roederer's avatar Alexander Roederer
Browse files

Fixes LE Sysui update to use new instead of old

Fixes a bug in Lifetime Extension Refactor where we were using the old
notification to post the update to system UI, instea dof the new
notification. In the case that the app cancels the notification, this
doesn't matter, because the old notification and the new will be the
same. In the case that the app UPDATES the notification, though, we want
SystemUI to reflect the state of the new notification, not the old one.
We have to check the old one for the presence of the flag (because apps
can't post notifications with the LE flag enabled, it gets removed in
fix notification), but we want to check the _contents_ of the new one
for the update, so that any changes the app made in response to the
direct reply are reflected.

Bug: 333131289
Test: atest NotificationManagerServiceTest, atest
NotificationListenersTest
Flag: ACONFIG android.app.lifetime_extension_refactor STAGING

Change-Id: Ic16eaa5815bddff9a3ddde7930d73508da9f61b4
parent 3fdc21c5
Loading
Loading
Loading
Loading
+14 −2
Original line number Original line Diff line number Diff line
@@ -139,6 +139,7 @@ import static android.service.notification.NotificationListenerService.TRIM_FULL
import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.contentprotection.flags.Flags.rapidClearNotificationsByListenerAppOpEnabled;
import static android.view.contentprotection.flags.Flags.rapidClearNotificationsByListenerAppOpEnabled;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
@@ -306,6 +307,7 @@ import android.view.Display;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager;
import android.widget.RemoteViews;
import android.widget.RemoteViews;
import android.widget.Toast;
import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;
@@ -357,7 +359,9 @@ import com.android.server.utils.quota.MultiRateLimiter;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.BackgroundActivityStartCallback;
import com.android.server.wm.BackgroundActivityStartCallback;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import libcore.io.IoUtils;
import libcore.io.IoUtils;
import org.json.JSONException;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserException;
@@ -11774,10 +11778,18 @@ public class NotificationManagerService extends SystemService {
                    }
                    }
                    if (lifetimeExtensionRefactor()) {
                    if (lifetimeExtensionRefactor()) {
                        if (sendRedacted && redactedSbn == null) {
                            redactedSbn = redactStatusBarNotification(sbn);
                            redactedCache = new TrimCache(redactedSbn);
                        }
                        final StatusBarNotification sbnToPost = sendRedacted
                                ? redactedCache.ForListener(info) : trimCache.ForListener(info);
                        // Checks if this is a request to notify system UI about a notification that
                        // Checks if this is a request to notify system UI about a notification that
                        // has been lifetime extended.
                        // has been lifetime extended.
                        // (We only need to check old for the flag, because in both cancellation and
                        // (We only need to check old for the flag, because in both cancellation and
                        // update cases, old should have the flag.)
                        // update cases, old should have the flag, whereas in update cases the
                        // new will NOT have the flag.)
                        // If it is such a request, and this is system UI, we send the post request
                        // If it is such a request, and this is system UI, we send the post request
                        // only to System UI, and break as we don't need to continue checking other
                        // only to System UI, and break as we don't need to continue checking other
                        // Managed Services.
                        // Managed Services.
@@ -11785,7 +11797,7 @@ public class NotificationManagerService extends SystemService {
                                && (old.getNotification().flags
                                && (old.getNotification().flags
                                & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
                                & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
                            final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
                            final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
                            listenerCalls.add(() -> notifyPosted(info, oldSbn, update));
                            listenerCalls.add(() -> notifyPosted(info, sbnToPost, update));
                            break;
                            break;
                        }
                        }
                    }
                    }
+69 −0
Original line number Original line Diff line number Diff line
@@ -70,6 +70,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserHandle;
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.INotificationListener;
import android.service.notification.INotificationListener;
import android.service.notification.IStatusBarNotificationHolder;
import android.service.notification.NotificationListenerFilter;
import android.service.notification.NotificationListenerFilter;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationRankingUpdate;
import android.service.notification.NotificationRankingUpdate;
@@ -90,6 +91,7 @@ import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoAnnotations;
@@ -154,6 +156,11 @@ public class NotificationListenersTest extends UiServiceTestCase {
                .thenReturn(new ArrayList<>());
                .thenReturn(new ArrayList<>());
        mNm.mHandler = mock(NotificationManagerService.WorkerHandler.class);
        mNm.mHandler = mock(NotificationManagerService.WorkerHandler.class);
        mNm.mAssistants = mock(NotificationManagerService.NotificationAssistants.class);
        mNm.mAssistants = mock(NotificationManagerService.NotificationAssistants.class);
        FieldSetter.setField(mNm,
                NotificationManagerService.class.getDeclaredField("mListeners"),
                mListeners);
        doReturn(android.service.notification.NotificationListenerService.TRIM_FULL)
                .when(mListeners).getOnNotificationPostedTrim(any());
    }
    }


    @Test
    @Test
@@ -827,6 +834,68 @@ public class NotificationListenersTest extends UiServiceTestCase {
        verify(mListeners, never()).redactStatusBarNotification(eq(sbn));
        verify(mListeners, never()).redactStatusBarNotification(eq(sbn));
    }
    }


    @Test
    public void testListenerPost_UpdateLifetimeExtended() throws Exception {
        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);

        // Create original notification, with FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY.
        String pkg = "pkg";
        int uid = 9;
        UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
        NotificationChannel channel = new NotificationChannel("id", "name",
                NotificationManager.IMPORTANCE_HIGH);
        Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
                .setContentTitle("foo")
                .setSmallIcon(android.R.drawable.sym_def_app_icon)
                .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true);
        StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0,
                nb.build(), userHandle, null, 0);
        NotificationRecord old = new NotificationRecord(mContext, sbn, channel);

        // Creates updated notification (without FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY)
        Notification.Builder nb2 = new Notification.Builder(mContext, channel.getId())
                .setContentTitle("new title")
                .setSmallIcon(android.R.drawable.sym_def_app_icon)
                .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, false);
        StatusBarNotification sbn2 = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0,
                nb2.build(), userHandle, null, 0);
        NotificationRecord toPost = new NotificationRecord(mContext, sbn2, channel);

        // Create system ui-like service.
        ManagedServices.ManagedServiceInfo info = mListeners.new ManagedServiceInfo(
                null, new ComponentName("a", "a"), sbn2.getUserId(), false, null, 33, 33);
        info.isSystemUi = true;
        INotificationListener l1 = mock(INotificationListener.class);
        info.service = l1;
        List<ManagedServices.ManagedServiceInfo> services = ImmutableList.of(info);
        when(mListeners.getServices()).thenReturn(services);

        FieldSetter.setField(mNm,
                NotificationManagerService.class.getDeclaredField("mHandler"),
                mock(NotificationManagerService.WorkerHandler.class));
        doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any());
        doReturn(mock(NotificationRankingUpdate.class)).when(mNm).makeRankingUpdateLocked(info);
        doReturn(false).when(mNm).isInLockDownMode(anyInt());
        doNothing().when(mNm).updateUriPermissions(any(), any(), any(), anyInt());
        doReturn(sbn2).when(mListeners).redactStatusBarNotification(sbn2);
        doReturn(sbn2).when(mListeners).redactStatusBarNotification(any());

        // The notification change is posted to the service listener.
        mListeners.notifyPostedLocked(toPost, old);

        // Verify that the post occcurs with the updated notification value.
        ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
        verify(mNm.mHandler, times(1)).post(runnableCaptor.capture());
        runnableCaptor.getValue().run();
        ArgumentCaptor<IStatusBarNotificationHolder> sbnCaptor =
                ArgumentCaptor.forClass(IStatusBarNotificationHolder.class);
        verify(l1, times(1)).onNotificationPosted(sbnCaptor.capture(), any());
        StatusBarNotification sbnResult = sbnCaptor.getValue().get();
        assertThat(sbnResult.getNotification()
                .extras.getCharSequence(Notification.EXTRA_TITLE).toString())
                .isEqualTo("new title");
    }

    /**
    /**
     * Helper method to test the thread safety of some operations.
     * Helper method to test the thread safety of some operations.
     *
     *
+50 −3
Original line number Original line Diff line number Diff line
@@ -106,6 +106,7 @@ import static android.service.notification.NotificationListenerService.Ranking.U
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
@@ -118,11 +119,11 @@ import static com.android.server.notification.NotificationManagerService.TAG;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth.assertWithMessage;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNotNull;
@@ -131,6 +132,7 @@ import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import static junit.framework.Assert.fail;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.isNull;
@@ -157,6 +159,9 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.when;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import android.Manifest;
import android.Manifest;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SuppressLint;
@@ -270,8 +275,10 @@ import android.util.Pair;
import android.util.Xml;
import android.util.Xml;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager;
import android.widget.RemoteViews;
import android.widget.RemoteViews;
import androidx.test.InstrumentationRegistry;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.internal.R;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.config.sysui.TestableFlagResolver;
import com.android.internal.config.sysui.TestableFlagResolver;
@@ -303,10 +310,12 @@ import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.utils.quota.MultiRateLimiter;
import com.android.server.utils.quota.MultiRateLimiter;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import com.google.android.collect.Lists;
import com.google.android.collect.Lists;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList;
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
import org.junit.After;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Before;
@@ -5933,6 +5942,45 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        assertThat(captor.getValue().shouldPostSilently()).isTrue();
        assertThat(captor.getValue().shouldPostSilently()).isTrue();
    }
    }
    @Test
    @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
    public void testUpdate_DirectReplyLifetimeExtendedUpdateSucceeds() throws Exception {
        // Creates a lifetime extended notification.
        NotificationRecord original = generateNotificationRecord(mTestNotificationChannel);
        original.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
        mService.addNotification(original);
        // Post an update for that notification.
        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, original.getSbn().getId(),
                original.getSbn().getTag(), mUid, 0,
                new Notification.Builder(mContext, mTestNotificationChannel.getId())
                        .setSmallIcon(android.R.drawable.sym_def_app_icon)
                        .setContentTitle("new title").build(),
                UserHandle.getUserHandleForUid(mUid), null, 0);
        NotificationRecord update = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
        mService.addEnqueuedNotification(update);
        NotificationManagerService.PostNotificationRunnable runnable =
                mService.new PostNotificationRunnable(update.getKey(),
                        update.getSbn().getPackageName(),
                        update.getUid(),
                        mPostNotificationTrackerFactory.newTracker(null));
        runnable.run();
        waitForIdle();
        // Checks the update was sent, and that update contains the new title, and does not contain
        // the lifetime extension flag.
        ArgumentCaptor<NotificationRecord> captor =
                ArgumentCaptor.forClass(NotificationRecord.class);
        verify(mListeners, times(1)).prepareNotifyPostedLocked(captor.capture(), any(),
                anyBoolean());
        assertThat(captor.getValue().getNotification().flags
                & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(0);
        assertThat(captor.getValue()
                .getNotification().extras.getCharSequence(Notification.EXTRA_TITLE).toString())
                .isEqualTo("new title");
    }
    @Test
    @Test
    public void testStats_updatedOnUserExpansion() throws Exception {
    public void testStats_updatedOnUserExpansion() throws Exception {
        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
@@ -12602,7 +12650,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        // the notifyPostedLocked function is called twice.
        // the notifyPostedLocked function is called twice.
        verify(mWorkerHandler, times(2)).postDelayed(any(Runnable.class), anyLong());
        verify(mWorkerHandler, times(2)).postDelayed(any(Runnable.class), anyLong());
        //verify(mListeners, times(2)).notifyPostedLocked(any(), any());
    }
    }
    @Test
    @Test