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

Commit 8c977c7e authored by Jeff Davidson's avatar Jeff Davidson
Browse files

Clean up SMS filter handling.

Define an interface for filters and perform filtering as a generic
iteration over the list of filters. Lower code duplication by
providing the list of remaining filters to each filter, allowing
an asynchronous filter (like the carrier filter) to invoke any.
remaining filters generically.

This should mostly be a no-op. However, there is one small behavioral
change - the MissedIncomingCallSmsFilter will now be invoked for
messages which are passed through and not dropped by the carrier SMS
filter and the visual voicemail SMS filter. This seems like it should
be the case for consistency.

Also allow overriding SMS filters for testing and add unit tests to
exercise filter invocation.

This is a precursor CL to one which will pass SMSes from blocked
numbers through the filters to prevent vital system messages from
being blocked.

Bug: 136262737
Test: atest GsmInboundSmsHandlerTest
Change-Id: I280d8e4c0847bf261e0497f95231a5063dfa025f
Merged-In: I280d8e4c0847bf261e0497f95231a5063dfa025f
parent 26fcb83f
Loading
Loading
Loading
Loading
+115 −48
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import static android.provider.Telephony.Sms.Intents.RESULT_SMS_NULL_PDU;
import static android.service.carrier.CarrierMessagingService.RECEIVE_OPTIONS_SKIP_NOTIFY_WHEN_CREDENTIAL_PROTECTED_STORAGE_UNAVAILABLE;
import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;

import android.annotation.Nullable;
import android.app.Activity;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
@@ -84,6 +85,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

/**
@@ -164,7 +166,7 @@ public abstract class InboundSmsHandler extends StateMachine {
    public static final int EVENT_BROADCAST_SMS = 2;

    /** Message from resultReceiver notifying {@link WaitingState} of a completed broadcast. */
    private static final int EVENT_BROADCAST_COMPLETE = 3;
    public static final int EVENT_BROADCAST_COMPLETE = 3;

    /** Sent on exit from {@link WaitingState} to return to idle after sending all broadcasts. */
    private static final int EVENT_RETURN_TO_IDLE = 4;
@@ -260,6 +262,8 @@ public abstract class InboundSmsHandler extends StateMachine {
        others in order to update metrics. */
    private boolean mLastSmsWasInjected = false;

    private List<SmsFilter> mSmsFilters;

    /**
     * Create a new SMS broadcast helper.
     * @param name the class name for logging
@@ -289,6 +293,8 @@ public abstract class InboundSmsHandler extends StateMachine {
                (PowerWhitelistManager) mContext.getSystemService(Context.POWER_WHITELIST_MANAGER);
        mCellBroadcastServiceManager = new CellBroadcastServiceManager(context, phone);

        mSmsFilters = createDefaultSmsFilters();

        addState(mDefaultState);
        addState(mStartupState, mDefaultState);
        addState(mIdleState, mDefaultState);
@@ -1136,53 +1142,84 @@ public abstract class InboundSmsHandler extends StateMachine {
    }

    /**
     * Filters the SMS.
     *
     * <p>currently 3 filters exists: the carrier package, the system package, and the
     * VisualVoicemailSmsFilter.
     *
     * <p>The filtering process is:
     *
     * <p>If the carrier package exists, the SMS will be filtered with it first. If the carrier
     * package did not drop the SMS, then the VisualVoicemailSmsFilter will filter it in the
     * callback.
     * Creates the default filters used to filter SMS messages.
     *
     * <p>If the carrier package does not exists, we will let the VisualVoicemailSmsFilter filter
     * it. If the SMS passed the filter, then we will try to find the system package to do the
     * filtering.
     * <p>Currently 3 filters exist: the carrier package, the VisualVoicemailSmsFilter, and the
     * missed incoming call SMS filter.
     *
     * @return true if a filter is invoked and the SMS processing flow is diverted, false otherwise.
     * <p>Since the carrier filter is asynchronous, if a message passes through the carrier filter,
     * the remaining filters will be applied in the callback.
     */
    private boolean filterSms(byte[][] pdus, int destPort,
        InboundSmsTracker tracker, SmsBroadcastReceiver resultReceiver, boolean userUnlocked) {
    private List<SmsFilter> createDefaultSmsFilters() {
        List<SmsFilter> smsFilters = new ArrayList<>(3);
        smsFilters.add(
                (pdus, destPort, tracker, resultReceiver, userUnlocked, remainingFilters) -> {
                    CarrierServicesSmsFilterCallback filterCallback =
                            new CarrierServicesSmsFilterCallback(
                        pdus, destPort, tracker.getFormat(), resultReceiver, userUnlocked,
                        tracker.isClass0(), tracker.getSubId(), tracker.getMessageId());
                                    pdus, destPort, tracker, tracker.getFormat(), resultReceiver,
                                    userUnlocked,
                                    tracker.isClass0(), tracker.getSubId(), tracker.getMessageId(),
                                    remainingFilters);
                    CarrierServicesSmsFilter carrierServicesFilter = new CarrierServicesSmsFilter(
                            mContext, mPhone, pdus, destPort, tracker.getFormat(),
                filterCallback, getName() + "::CarrierServicesSmsFilter", mCarrierServiceLocalLog,
                tracker.getMessageId());
                            filterCallback, getName() + "::CarrierServicesSmsFilter",
                            mCarrierServiceLocalLog, tracker.getMessageId());
                    if (carrierServicesFilter.filter()) {
            log("filterSms: SMS is being handled by carrier service", tracker.getMessageId());
                        log("SMS is being handled by carrier service", tracker.getMessageId());
                        return true;
                    } else {
                        return false;
                    }

                });
        smsFilters.add(
                (pdus, destPort, tracker, resultReceiver, userUnlocked, remainingFilters) -> {
                    if (VisualVoicemailSmsFilter.filter(
                            mContext, pdus, tracker.getFormat(), destPort, tracker.getSubId())) {
            logWithLocalLog("filterSms: Visual voicemail SMS dropped", tracker.getMessageId());
                        logWithLocalLog("Visual voicemail SMS dropped", tracker.getMessageId());
                        dropSms(resultReceiver);
                        return true;
                    }

                    return false;
                });
        smsFilters.add(
                (pdus, destPort, tracker, resultReceiver, userUnlocked, remainingFilters) -> {
                    MissedIncomingCallSmsFilter missedIncomingCallSmsFilter =
                            new MissedIncomingCallSmsFilter(mPhone);
                    if (missedIncomingCallSmsFilter.filter(pdus, tracker.getFormat())) {
            logWithLocalLog("filterSms: Missed incoming call SMS received", tracker.getMessageId());
                        logWithLocalLog("Missed incoming call SMS received",
                                tracker.getMessageId());
                        dropSms(resultReceiver);
                        return true;
                    }
                    return false;
                });
        return smsFilters;
    }

    /**
     * Filters the SMS.
     *
     * <p>Each filter in {@link #mSmsFilters} is invoked sequentially. If any filter returns true,
     * this method returns true and subsequent filters are ignored.
     *
     * @return true if a filter is invoked and the SMS processing flow is diverted, false otherwise.
     */
    private boolean filterSms(byte[][] pdus, int destPort,
            InboundSmsTracker tracker, SmsBroadcastReceiver resultReceiver, boolean userUnlocked) {
        return filterSms(pdus, destPort, tracker, resultReceiver, userUnlocked, mSmsFilters);
    }

    private static boolean filterSms(byte[][] pdus, int destPort,
            InboundSmsTracker tracker, SmsBroadcastReceiver resultReceiver, boolean userUnlocked,
            List<SmsFilter> filters) {
        ListIterator<SmsFilter> iterator = filters.listIterator();
        while (iterator.hasNext()) {
            SmsFilter smsFilter = iterator.next();
            if (smsFilter.filterSms(pdus, destPort, tracker, resultReceiver, userUnlocked,
                    filters.subList(iterator.nextIndex(), filters.size()))) {
                return true;
            }
        }
        return false;
    }

@@ -1527,7 +1564,8 @@ public abstract class InboundSmsHandler extends StateMachine {
     * Handler for an {@link InboundSmsTracker} broadcast. Deletes PDUs from the raw table and
     * logs the broadcast duration (as an error if the other receivers were especially slow).
     */
    private final class SmsBroadcastReceiver extends BroadcastReceiver {
    @VisibleForTesting
    public final class SmsBroadcastReceiver extends BroadcastReceiver {
        @UnsupportedAppUsage
        private final String mDeleteWhere;
        @UnsupportedAppUsage
@@ -1612,34 +1650,37 @@ public abstract class InboundSmsHandler extends StateMachine {
            CarrierServicesSmsFilter.CarrierServicesSmsFilterCallbackInterface {
        private final byte[][] mPdus;
        private final int mDestPort;
        private final InboundSmsTracker mTracker;
        private final String mSmsFormat;
        private final SmsBroadcastReceiver mSmsBroadcastReceiver;
        private final boolean mUserUnlocked;
        private final boolean mIsClass0;
        private final int mSubId;
        private final long mMessageId;
        private final List<SmsFilter> mRemainingFilters;

        CarrierServicesSmsFilterCallback(byte[][] pdus, int destPort, String smsFormat,
                SmsBroadcastReceiver smsBroadcastReceiver,  boolean userUnlocked,
                boolean isClass0, int subId, long messageId) {
        CarrierServicesSmsFilterCallback(byte[][] pdus, int destPort, InboundSmsTracker tracker,
                String smsFormat, SmsBroadcastReceiver smsBroadcastReceiver, boolean userUnlocked,
                boolean isClass0, int subId, long messageId, List<SmsFilter> remainingFilters) {
            mPdus = pdus;
            mDestPort = destPort;
            mTracker = tracker;
            mSmsFormat = smsFormat;
            mSmsBroadcastReceiver = smsBroadcastReceiver;
            mUserUnlocked = userUnlocked;
            mIsClass0 = isClass0;
            mSubId = subId;
            mMessageId = messageId;
            mRemainingFilters = remainingFilters;
        }

        @Override
        public void onFilterComplete(int result) {
            log("onFilterComplete: result is " + result, mMessageId);
            if ((result & CarrierMessagingService.RECEIVE_OPTIONS_DROP) == 0) {
                if (VisualVoicemailSmsFilter.filter(mContext, mPdus,
                        mSmsFormat, mDestPort, mSubId)) {
                    logWithLocalLog("Visual voicemail SMS dropped", mMessageId);
                    dropSms(mSmsBroadcastReceiver);
                // Message isn't dropped, so run it through the remaining filters.
                if (filterSms(mPdus, mDestPort, mTracker, mSmsBroadcastReceiver, mUserUnlocked,
                        mRemainingFilters)) {
                    return;
                }

@@ -1884,6 +1925,20 @@ public abstract class InboundSmsHandler extends StateMachine {
        mWakeLockTimeout = timeOut;
    }

    /**
     * Set the SMS filters used by {@link #filterSms} for testing purposes.
     *
     * @param smsFilters List of SMS filters, or null to restore the default filters.
     */
    @VisibleForTesting
    public void setSmsFiltersForTesting(@Nullable List<SmsFilter> smsFilters) {
        if (smsFilters == null) {
            mSmsFilters = createDefaultSmsFilters();
        } else {
            mSmsFilters = smsFilters;
        }
    }

    /**
     * Handler for the broadcast sent when the new message notification is clicked. It launches the
     * default SMS app.
@@ -1963,4 +2018,16 @@ public abstract class InboundSmsHandler extends StateMachine {
            }
        }
    }

    /** A filter for incoming messages allowing the normal processing flow to be skipped. */
    @VisibleForTesting
    public interface SmsFilter {
        /**
         * Returns true if a filter is invoked and the SMS processing flow should be diverted, false
         * otherwise.
         */
        boolean filterSms(byte[][] pdus, int destPort, InboundSmsTracker tracker,
                SmsBroadcastReceiver resultReceiver, boolean userUnlocked,
                List<SmsFilter> remainingFilters);
    }
}
+156 −1
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.nullable;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
@@ -34,6 +35,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.BroadcastReceiver;
import android.content.ContentValues;
@@ -75,9 +77,13 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
import org.mockito.verification.VerificationMode;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@RunWith(AndroidTestingRunner.class)
@@ -102,6 +108,11 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {
    private ContentValues mInboundSmsTrackerCVSub1;
    @Mock
    private CdmaInboundSmsHandler mCdmaInboundSmsHandler;
    @Mock
    private InboundSmsHandler.SmsFilter mSmsFilter;
    @Mock
    private InboundSmsHandler.SmsFilter mSmsFilter2;
    private List<InboundSmsHandler.SmsFilter> mSmsFilters;

    private GsmInboundSmsHandler mGsmInboundSmsHandler;

@@ -235,6 +246,10 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {

        mGsmInboundSmsHandler = GsmInboundSmsHandler.makeInboundSmsHandler(mContext,
                mSmsStorageMonitor, mPhone);
        mSmsFilters = new ArrayList<>();
        mSmsFilters.add(mSmsFilter);
        mSmsFilters.add(mSmsFilter2);
        mGsmInboundSmsHandler.setSmsFiltersForTesting(mSmsFilters);
        monitorTestableLooper(new TestableLooper(mGsmInboundSmsHandler.getHandler().getLooper()));
        processAllMessages();
    }
@@ -342,6 +357,8 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {

        verify(mContext, times(2)).sendBroadcast(any(Intent.class));
        assertEquals("IdleState", getCurrentState().getName());

        verifySmsFiltersInvoked(times(1));
    }

    @Test
@@ -356,6 +373,67 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {

        verify(mContext, never()).sendBroadcast(any(Intent.class));
        assertEquals("IdleState", getCurrentState().getName());

        // verify no filter was invoked.
        // TODO(b/136262737): Adjust test once blocked SMSes are passed through filters too.
        verifySmsFiltersInvoked(never());
    }

    @Test
    @MediumTest
    public void testNewSms_filterInvoked_noBroadcastsSent() {
        // Configure the first filter to drop the SMS.
        when(mSmsFilter.filterSms(any(byte[][].class), anyInt(),
                any(InboundSmsTracker.class), any(InboundSmsHandler.SmsBroadcastReceiver.class),
                anyBoolean(), Mockito.<List<InboundSmsHandler.SmsFilter>>any()))
                .thenAnswer((Answer<Boolean>) invocation -> {
                    mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_BROADCAST_COMPLETE);
                    return true;
                });

        transitionFromStartupToIdle();

        sendNewSms();

        verify(mContext, never()).sendBroadcast(any(Intent.class));
        assertEquals("IdleState", getCurrentState().getName());

        // verify second filter was never invoked.
        verify(mSmsFilter2, never()).filterSms(any(byte[][].class), anyInt(),
                any(InboundSmsTracker.class), any(InboundSmsHandler.SmsBroadcastReceiver.class),
                anyBoolean(), Mockito.<List<InboundSmsHandler.SmsFilter>>any());
    }

    @Test
    @MediumTest
    public void testNewSms_filterChaining_noBroadcastsSent() {
        // Have the first filter indicate it matched without completing the flow.
        when(mSmsFilter.filterSms(any(byte[][].class), anyInt(),
                any(InboundSmsTracker.class), any(InboundSmsHandler.SmsBroadcastReceiver.class),
                anyBoolean(), Mockito.<List<InboundSmsHandler.SmsFilter>>any())).thenReturn(true);

        transitionFromStartupToIdle();

        sendNewSms();

        verify(mContext, never()).sendBroadcast(any(Intent.class));
        // Now waiting for the first filter to complete.
        assertEquals("WaitingState", getCurrentState().getName());

        // Verify the first filter was invoked with the right set of remaining filters.
        verify(mSmsFilter).filterSms(any(byte[][].class), anyInt(),
                any(InboundSmsTracker.class), any(InboundSmsHandler.SmsBroadcastReceiver.class),
                anyBoolean(), eq(Collections.singletonList(mSmsFilter2)));

        // Verify second filter was never invoked.
        verify(mSmsFilter2, never()).filterSms(any(byte[][].class), anyInt(),
                any(InboundSmsTracker.class), any(InboundSmsHandler.SmsBroadcastReceiver.class),
                anyBoolean(), Mockito.<List<InboundSmsHandler.SmsFilter>>any());

        // Clean up by completing the broadcast, as an asynchronous filter must do.
        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_BROADCAST_COMPLETE);
        processAllMessages();
        assertEquals("IdleState", getCurrentState().getName());
    }

    private void verifyDataSmsIntentBroadcasts(int numPastBroadcasts) {
@@ -402,6 +480,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {
        processAllMessages();

        verifySmsIntentBroadcasts(0, true /* allowBgActivityStarts */);
        verifySmsFiltersInvoked(times(1));
    }

    @Test
@@ -439,6 +518,8 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {
        processAllMessages();

        verifyDataSmsIntentBroadcasts(1);

        verifySmsFiltersInvoked(times(2));
    }

    @FlakyTest
@@ -460,6 +541,8 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {

        verify(mContext, times(2)).sendBroadcast(any(Intent.class));
        assertEquals("IdleState", getCurrentState().getName());

        verifySmsFiltersInvoked(times(1));
    }

    private void prepareMultiPartSms(boolean is3gpp2WapPush) {
@@ -539,6 +622,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {

        // verify no broadcast sent.
        verify(mContext, times(0)).sendBroadcast(any(Intent.class));
        verifySmsFiltersInvoked(never());

        // additional copy of part 1 of non-3gpp2wap
        prepareMultiPartSms(false);
@@ -555,6 +639,8 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {
        // verify there are three segments in the db and only one of them is not marked as deleted.
        assertEquals(3, mContentProvider.getNumRows());
        assertEquals(1, mContentProvider.query(sRawUri, null, "deleted=0", null, null).getCount());

        verifySmsFiltersInvoked(times(1));
    }

    @FlakyTest
@@ -588,6 +674,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {

        // verify broadcast intents
        verifySmsIntentBroadcasts(0);
        verifySmsFiltersInvoked(times(1));

        // if an additional copy of one of the segments above is received, it should not be kept in
        // the db and should not be combined with any subsequent messages received from the same
@@ -603,6 +690,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {

        // verify no additional broadcasts sent
        verify(mContext, times(2)).sendBroadcast(any(Intent.class));
        verifySmsFiltersInvoked(times(1));

        // part 1 of new sms recieved from same sender with same parameters, just different
        // timestamps, should not be combined with the additional part 2 received above
@@ -620,6 +708,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {

        // verify no additional broadcasts sent
        verify(mContext, times(2)).sendBroadcast(any(Intent.class));
        verifySmsFiltersInvoked(times(1));

        assertEquals("IdleState", getCurrentState().getName());
    }
@@ -682,6 +771,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {
        assertEquals(mMessageBodyPart2, c.getString(c.getColumnIndex("message_body")));
        // State machine should go back to idle
        assertEquals("IdleState", getCurrentState().getName());
        verifySmsFiltersInvoked(never());
    }

    @Test
@@ -737,6 +827,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {
        verify(mContext, never()).sendBroadcast(any(Intent.class));
        // State machine should go back to idle
        assertEquals("IdleState", getCurrentState().getName());
        verifySmsFiltersInvoked(never());
    }

    @Test
@@ -771,6 +862,8 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {

        verify(mContext, never()).sendBroadcast(any(Intent.class));
        assertEquals("IdleState", getCurrentState().getName());
        // TODO(b/136262737): Adjust test once blocked SMSes are passed through filters too.
        verifySmsFiltersInvoked(never());
    }

    @Test
@@ -821,6 +914,55 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {

        verify(mContext, never()).sendBroadcast(any(Intent.class));
        assertEquals("IdleState", getCurrentState().getName());
        // TODO(b/136262737): Adjust test once blocked SMSes are passed through filters too.
        verifySmsFiltersInvoked(never());
    }

    @Test
    @MediumTest
    public void testMultipartSms_filterInvoked_noBroadcastsSent() {
        // Configure the first filter to drop the SMS.
        when(mSmsFilter.filterSms(any(byte[][].class), anyInt(),
                any(InboundSmsTracker.class), any(InboundSmsHandler.SmsBroadcastReceiver.class),
                anyBoolean(), Mockito.<List<InboundSmsHandler.SmsFilter>>any()))
                .thenAnswer((Answer<Boolean>) invocation -> {
                    mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_BROADCAST_COMPLETE);
                    return true;
                });

        transitionFromStartupToIdle();

        // prepare SMS part 1 and part 2
        prepareMultiPartSms(false);

        mSmsHeader.concatRef = new SmsHeader.ConcatRef();
        doReturn(mSmsHeader).when(mGsmSmsMessage).getUserDataHeader();

        doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory)
                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
                        anyInt(), anyBoolean(),
                        nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
        sendNewSms();

        // State machine should go back to idle and wait for second part
        assertEquals("IdleState", getCurrentState().getName());

        doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
                        anyInt(), anyBoolean(),
                        nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
        sendNewSms();

        // verify no broadcasts sent
        verify(mContext, never()).sendBroadcast(any(Intent.class));
        assertEquals("IdleState", getCurrentState().getName());

        // verify second filter was never invoked.
        verify(mSmsFilter2, never()).filterSms(any(byte[][].class), anyInt(),
                any(InboundSmsTracker.class), any(InboundSmsHandler.SmsBroadcastReceiver.class),
                anyBoolean(), Mockito.<List<InboundSmsHandler.SmsFilter>>any());
    }

    @Test
@@ -860,6 +1002,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {
        processAllMessages();

        verifyDataSmsIntentBroadcasts(1);
        verifySmsFiltersInvoked(times(1));
    }

    @Test
@@ -880,6 +1023,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {

        // user is unlocked; intent should be broadcast right away
        verifyDataSmsIntentBroadcasts(0);
        verifySmsFiltersInvoked(times(1));
    }

    @Test
@@ -918,7 +1062,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {

        verify(mContext, times(1)).sendBroadcast(any(Intent.class));
        assertEquals("IdleState", getCurrentState().getName());

        verifySmsFiltersInvoked(never());
    }

    @FlakyTest
@@ -945,6 +1089,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {
        processAllMessages();

        verifySmsIntentBroadcasts(0);
        verifySmsFiltersInvoked(times(1));
    }

    @Test
@@ -963,5 +1108,15 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest {

        verifySmsIntentBroadcasts(0, mSubId0, true);
        verifySmsIntentBroadcasts(2, mSubId1, false);
        verifySmsFiltersInvoked(times(2));
    }

    private void verifySmsFiltersInvoked(VerificationMode verificationMode) {
        verify(mSmsFilter, verificationMode).filterSms(any(byte[][].class), anyInt(),
                any(InboundSmsTracker.class), any(InboundSmsHandler.SmsBroadcastReceiver.class),
                anyBoolean(), Mockito.<List<InboundSmsHandler.SmsFilter>>any());
        verify(mSmsFilter2, verificationMode).filterSms(any(byte[][].class), anyInt(),
                any(InboundSmsTracker.class), any(InboundSmsHandler.SmsBroadcastReceiver.class),
                anyBoolean(), Mockito.<List<InboundSmsHandler.SmsFilter>>any());
    }
}