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

Commit 1ade2aac authored by Alexander Roederer's avatar Alexander Roederer Committed by Android (Google) Code Review
Browse files

Merge changes from topic "b284297289-SeparateSmartActionFromAshmem" into main

* changes:
  Removes SmartActions from Rankings before marshal
  Moves test utils from NLSTest to RankingTest
parents d5c6e8ff 43140005
Loading
Loading
Loading
Loading
+14 −2
Original line number Diff line number Diff line
@@ -2007,6 +2007,20 @@ public abstract class NotificationListenerService extends Service {
            return mSmartActions == null ? Collections.emptyList() : mSmartActions;
        }


        /**
         * Sets the smart {@link Notification.Action} objects.
         *
         * Should ONLY be used in cases where smartActions need to be removed from, then restored
         * on, Ranking objects during Parceling, when they are transmitted between processes via
         * Shared Memory.
         *
         * @hide
         */
        public void setSmartActions(@Nullable ArrayList<Notification.Action> smartActions) {
            mSmartActions = smartActions;
        }

        /**
         * Returns a list of smart replies that can be added by the
         * {@link NotificationAssistantService}
@@ -2353,11 +2367,9 @@ public abstract class NotificationListenerService extends Service {

        /**
         * Get a reference to the actual Ranking object corresponding to the key.
         * Used only by unit tests.
         *
         * @hide
         */
        @VisibleForTesting
        public Ranking getRawRankingObject(String key) {
            return mRankings.get(key);
        }
+78 −8
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package android.service.notification;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.app.Notification;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SharedMemory;
@@ -29,9 +31,12 @@ import androidx.annotation.NonNull;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

/**
 * Represents an update to notification rankings.
 *
 * @hide
 */
@SuppressLint({"ParcelNotFinal", "ParcelCreator"})
@@ -64,6 +69,7 @@ public class NotificationRankingUpdate implements Parcelable {
                // The ranking map should be stored in shared memory when it is parceled, so we
                // unwrap the SharedMemory object.
                mRankingMapFd = in.readParcelable(getClass().getClassLoader(), SharedMemory.class);
                Bundle smartActionsBundle = in.readBundle(getClass().getClassLoader());

                // In the case that the ranking map can't be read, readParcelable may return null.
                // In this case, we set mRankingMap to null;
@@ -82,8 +88,13 @@ public class NotificationRankingUpdate implements Parcelable {
                mapParcel.unmarshall(payload, 0, payload.length);
                mapParcel.setDataPosition(0);

                mRankingMap = mapParcel.readParcelable(getClass().getClassLoader(),
                        android.service.notification.NotificationListenerService.RankingMap.class);
                mRankingMap =
                        mapParcel.readParcelable(
                                getClass().getClassLoader(),
                                NotificationListenerService.RankingMap.class);

                addSmartActionsFromBundleToRankingMap(smartActionsBundle);

            } catch (ErrnoException e) {
                // TODO(b/284297289): remove throw when associated flag is moved to droidfood, to
                // avoid crashes; change to Log.wtf.
@@ -101,8 +112,32 @@ public class NotificationRankingUpdate implements Parcelable {
        }
    }

    /**
     * For each key in the rankingMap, extracts lists of smart actions stored in the provided
     * bundle and adds them to the corresponding Ranking object in the provided ranking
     * map, then returns the rankingMap.
     *
     * @hide
     */
    private void addSmartActionsFromBundleToRankingMap(Bundle smartActionsBundle) {
        if (smartActionsBundle == null) {
            return;
        }

        String[] rankingMapKeys = mRankingMap.getOrderedKeys();
        for (int i = 0; i < rankingMapKeys.length; i++) {
            String key = rankingMapKeys[i];
            ArrayList<Notification.Action> smartActions =
                    smartActionsBundle.getParcelableArrayList(key, Notification.Action.class);
            // Get the ranking object from the ranking map.
            NotificationListenerService.Ranking ranking = mRankingMap.getRawRankingObject(key);
            ranking.setSmartActions(smartActions);
        }
    }

    /**
     * Confirms that the SharedMemory file descriptor is closed. Should only be used for testing.
     *
     * @hide
     */
    @TestApi
@@ -145,9 +180,45 @@ public class NotificationRankingUpdate implements Parcelable {
        if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
                SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
            final Parcel mapParcel = Parcel.obtain();
            ArrayList<NotificationListenerService.Ranking> marshalableRankings = new ArrayList<>();
            Bundle smartActionsBundle = new Bundle();

            // We need to separate the SmartActions from the RankingUpdate objects.
            // SmartActions can contain PendingIntents, which cannot be marshalled,
            // so we extract them to send separately.
            String[] rankingMapKeys = mRankingMap.getOrderedKeys();
            for (int i = 0; i < rankingMapKeys.length; i++) {
                String key = rankingMapKeys[i];
                NotificationListenerService.Ranking ranking = mRankingMap.getRawRankingObject(key);

                // Removes the SmartActions and stores them in a separate map.
                // Note that getSmartActions returns a Collections.emptyList() if there are no
                // smart actions, and we don't want to needlessly store an empty list object, so we
                // check for null before storing.
                List<Notification.Action> smartActions = ranking.getSmartActions();
                if (!smartActions.isEmpty()) {
                    smartActionsBundle.putParcelableList(key, smartActions);
                }

                // Create a copy of the ranking object that doesn't have the smart actions.
                NotificationListenerService.Ranking rankingCopy =
                        new NotificationListenerService.Ranking();
                rankingCopy.populate(ranking);
                rankingCopy.setSmartActions(null);
                marshalableRankings.add(rankingCopy);
            }

            // Create a new marshalable RankingMap.
            NotificationListenerService.RankingMap marshalableRankingMap =
                    new NotificationListenerService.RankingMap(
                            marshalableRankings.toArray(
                                    new NotificationListenerService.Ranking[0]
                            )
                    );

            try {
                // Parcels the ranking map and measures its size.
                mapParcel.writeParcelable(mRankingMap, flags);
                mapParcel.writeParcelable(marshalableRankingMap, flags);
                int mapSize = mapParcel.dataSize();

                // Creates a new SharedMemory object with enough space to hold the ranking map.
@@ -158,15 +229,14 @@ public class NotificationRankingUpdate implements Parcelable {

                // Gets a read/write buffer mapping the entire shared memory region.
                final ByteBuffer buffer = mRankingMapFd.mapReadWrite();

                // Puts the ranking map into the shared memory region buffer.
                buffer.put(mapParcel.marshall(), 0, mapSize);

                // Protects the region from being written to, by setting it to be read-only.
                mRankingMapFd.setProtect(OsConstants.PROT_READ);

                // Puts the SharedMemory object in the parcel.
                out.writeParcelable(mRankingMapFd, flags);
                // Writes the Parceled smartActions separately.
                out.writeBundle(smartActionsBundle);
            } catch (ErrnoException e) {
                // TODO(b/284297289): remove throw when associated flag is moved to droidfood, to
                // avoid crashes; change to Log.wtf.
+555 −50

File changed.

Preview size limit exceeded, changes collapsed.

+0 −122
Original line number Diff line number Diff line
@@ -23,9 +23,7 @@ import static android.service.notification.NotificationListenerService.Ranking.U

import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
@@ -49,12 +47,10 @@ import android.graphics.drawable.Icon;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.NotificationRankingUpdate;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
@@ -158,81 +154,6 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
        }
    }

    // Tests parceling of NotificationRankingUpdate, and by extension, RankingMap and Ranking.
    @Test
    public void testRankingUpdate_parcel() {
        NotificationRankingUpdate nru = generateUpdate();
        Parcel parcel = Parcel.obtain();
        nru.writeToParcel(parcel, 0);
        parcel.setDataPosition(0);
        NotificationRankingUpdate nru1 = NotificationRankingUpdate.CREATOR.createFromParcel(parcel);
        assertEquals(nru, nru1);
    }

    // Tests parceling of RankingMap and RankingMap.equals
    @Test
    public void testRankingMap_parcel() {
        RankingMap rmap = generateUpdate().getRankingMap();
        Parcel parcel = Parcel.obtain();
        rmap.writeToParcel(parcel, 0);
        parcel.setDataPosition(0);
        RankingMap rmap1 = RankingMap.CREATOR.createFromParcel(parcel);

        detailedAssertEquals(rmap, rmap1);
        assertEquals(rmap, rmap1);
    }

    // Tests parceling of Ranking and Ranking.equals
    @Test
    public void testRanking_parcel() {
        Ranking ranking = generateUpdate().getRankingMap().getRawRankingObject(mKeys[0]);
        Parcel parcel = Parcel.obtain();
        ranking.writeToParcel(parcel, 0);
        parcel.setDataPosition(0);
        Ranking ranking1 = new Ranking(parcel);
        detailedAssertEquals("rankings differ: ", ranking, ranking1);
        assertEquals(ranking, ranking1);
    }

    // Tests NotificationRankingUpdate.equals(), and by extension, RankingMap and Ranking.
    @Test
    public void testRankingUpdate_equals() {
        NotificationRankingUpdate nru = generateUpdate();
        NotificationRankingUpdate nru2 = generateUpdate();
        detailedAssertEquals(nru, nru2);
        assertEquals(nru, nru2);
        Ranking tweak = nru2.getRankingMap().getRawRankingObject(mKeys[0]);
        tweak.populate(
                tweak.getKey(),
                tweak.getRank(),
                !tweak.matchesInterruptionFilter(), // note the inversion here!
                tweak.getLockscreenVisibilityOverride(),
                tweak.getSuppressedVisualEffects(),
                tweak.getImportance(),
                tweak.getImportanceExplanation(),
                tweak.getOverrideGroupKey(),
                tweak.getChannel(),
                (ArrayList) tweak.getAdditionalPeople(),
                (ArrayList) tweak.getSnoozeCriteria(),
                tweak.canShowBadge(),
                tweak.getUserSentiment(),
                tweak.isSuspended(),
                tweak.getLastAudiblyAlertedMillis(),
                tweak.isNoisy(),
                (ArrayList) tweak.getSmartActions(),
                (ArrayList) tweak.getSmartReplies(),
                tweak.canBubble(),
                tweak.isTextChanged(),
                tweak.isConversation(),
                tweak.getConversationShortcutInfo(),
                tweak.getRankingAdjustment(),
                tweak.isBubble(),
                tweak.getProposedImportance(),
                tweak.hasSensitiveContent()
        );
        assertNotEquals(nru, nru2);
    }

    @Test
    public void testLegacyIcons_preM() {
        TestListenerService service = new TestListenerService();
@@ -275,7 +196,6 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
        assertNull(n.largeIcon);
    }


    // Test data

    private String[] mKeys = new String[] { "key", "key1", "key2", "key3", "key4"};
@@ -461,48 +381,6 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
        }
    }

    private void detailedAssertEquals(NotificationRankingUpdate a, NotificationRankingUpdate b) {
        detailedAssertEquals(a.getRankingMap(), b.getRankingMap());
    }

    private void detailedAssertEquals(String comment, Ranking a, Ranking b) {
        assertEquals(comment, a.getKey(), b.getKey());
        assertEquals(comment, a.getRank(), b.getRank());
        assertEquals(comment, a.matchesInterruptionFilter(), b.matchesInterruptionFilter());
        assertEquals(comment, a.getLockscreenVisibilityOverride(), b.getLockscreenVisibilityOverride());
        assertEquals(comment, a.getSuppressedVisualEffects(), b.getSuppressedVisualEffects());
        assertEquals(comment, a.getImportance(), b.getImportance());
        assertEquals(comment, a.getImportanceExplanation(), b.getImportanceExplanation());
        assertEquals(comment, a.getOverrideGroupKey(), b.getOverrideGroupKey());
        assertEquals(comment, a.getChannel(), b.getChannel());
        assertEquals(comment, a.getAdditionalPeople(), b.getAdditionalPeople());
        assertEquals(comment, a.getSnoozeCriteria(), b.getSnoozeCriteria());
        assertEquals(comment, a.canShowBadge(), b.canShowBadge());
        assertEquals(comment, a.getUserSentiment(), b.getUserSentiment());
        assertEquals(comment, a.isSuspended(), b.isSuspended());
        assertEquals(comment, a.getLastAudiblyAlertedMillis(), b.getLastAudiblyAlertedMillis());
        assertEquals(comment, a.isNoisy(), b.isNoisy());
        assertEquals(comment, a.getSmartReplies(), b.getSmartReplies());
        assertEquals(comment, a.canBubble(), b.canBubble());
        assertEquals(comment, a.isConversation(), b.isConversation());
        assertEquals(comment, a.getConversationShortcutInfo().getId(),
                b.getConversationShortcutInfo().getId());
        assertActionsEqual(a.getSmartActions(), b.getSmartActions());
        assertEquals(a.getProposedImportance(), b.getProposedImportance());
        assertEquals(a.hasSensitiveContent(), b.hasSensitiveContent());
    }

    private void detailedAssertEquals(RankingMap a, RankingMap b) {
        Ranking arank = new Ranking();
        Ranking brank = new Ranking();
        assertArrayEquals(a.getOrderedKeys(), b.getOrderedKeys());
        for (String key : a.getOrderedKeys()) {
            a.getRanking(key, arank);
            b.getRanking(key, brank);
            detailedAssertEquals("ranking for key <" + key + ">", arank, brank);
        }
    }

    public static class TestListenerService extends NotificationListenerService {
        private final IBinder binder = new LocalBinder();
        public int targetSdk = 0;