Loading src/java/com/android/internal/telephony/InboundSmsHandler.java +115 −48 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; /** Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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); Loading Loading @@ -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; } Loading Loading @@ -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 Loading Loading @@ -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; } Loading Loading @@ -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. Loading Loading @@ -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); } } tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java +156 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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) Loading @@ -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; Loading Loading @@ -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(); } Loading Loading @@ -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 Loading @@ -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) { Loading Loading @@ -402,6 +480,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { processAllMessages(); verifySmsIntentBroadcasts(0, true /* allowBgActivityStarts */); verifySmsFiltersInvoked(times(1)); } @Test Loading Loading @@ -439,6 +518,8 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { processAllMessages(); verifyDataSmsIntentBroadcasts(1); verifySmsFiltersInvoked(times(2)); } @FlakyTest Loading @@ -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) { Loading Loading @@ -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); Loading @@ -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 Loading Loading @@ -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 Loading @@ -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 Loading @@ -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()); } Loading Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -860,6 +1002,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { processAllMessages(); verifyDataSmsIntentBroadcasts(1); verifySmsFiltersInvoked(times(1)); } @Test Loading @@ -880,6 +1023,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { // user is unlocked; intent should be broadcast right away verifyDataSmsIntentBroadcasts(0); verifySmsFiltersInvoked(times(1)); } @Test Loading Loading @@ -918,7 +1062,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { verify(mContext, times(1)).sendBroadcast(any(Intent.class)); assertEquals("IdleState", getCurrentState().getName()); verifySmsFiltersInvoked(never()); } @FlakyTest Loading @@ -945,6 +1089,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { processAllMessages(); verifySmsIntentBroadcasts(0); verifySmsFiltersInvoked(times(1)); } @Test Loading @@ -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()); } } Loading
src/java/com/android/internal/telephony/InboundSmsHandler.java +115 −48 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; /** Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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); Loading Loading @@ -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; } Loading Loading @@ -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 Loading Loading @@ -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; } Loading Loading @@ -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. Loading Loading @@ -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); } }
tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java +156 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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) Loading @@ -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; Loading Loading @@ -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(); } Loading Loading @@ -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 Loading @@ -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) { Loading Loading @@ -402,6 +480,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { processAllMessages(); verifySmsIntentBroadcasts(0, true /* allowBgActivityStarts */); verifySmsFiltersInvoked(times(1)); } @Test Loading Loading @@ -439,6 +518,8 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { processAllMessages(); verifyDataSmsIntentBroadcasts(1); verifySmsFiltersInvoked(times(2)); } @FlakyTest Loading @@ -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) { Loading Loading @@ -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); Loading @@ -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 Loading Loading @@ -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 Loading @@ -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 Loading @@ -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()); } Loading Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -860,6 +1002,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { processAllMessages(); verifyDataSmsIntentBroadcasts(1); verifySmsFiltersInvoked(times(1)); } @Test Loading @@ -880,6 +1023,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { // user is unlocked; intent should be broadcast right away verifyDataSmsIntentBroadcasts(0); verifySmsFiltersInvoked(times(1)); } @Test Loading Loading @@ -918,7 +1062,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { verify(mContext, times(1)).sendBroadcast(any(Intent.class)); assertEquals("IdleState", getCurrentState().getName()); verifySmsFiltersInvoked(never()); } @FlakyTest Loading @@ -945,6 +1089,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { processAllMessages(); verifySmsIntentBroadcasts(0); verifySmsFiltersInvoked(times(1)); } @Test Loading @@ -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()); } }