Loading src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java +116 −27 Original line number Original line Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.telephony.VisualVoicemailSmsFilterSettings; import android.util.ArrayMap; import android.util.ArrayMap; import android.util.Log; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.VisualVoicemailSmsParser.WrappedMessageData; import com.android.internal.telephony.VisualVoicemailSmsParser.WrappedMessageData; import java.nio.ByteBuffer; import java.nio.ByteBuffer; Loading @@ -40,8 +41,25 @@ import java.util.List; import java.util.Map; import java.util.Map; import java.util.regex.Pattern; import java.util.regex.Pattern; /** * Filters SMS to {@link android.telephony.VisualVoicemailService}, based on the config from {@link * VisualVoicemailSmsFilterSettings}. The SMS is sent to telephony service which will do the actual * dispatching. */ public class VisualVoicemailSmsFilter { public class VisualVoicemailSmsFilter { /** * Interface to convert subIds so the logic can be replaced in tests. */ @VisibleForTesting public interface PhoneAccountHandleConverter { /** * Convert the subId to a {@link PhoneAccountHandle} */ PhoneAccountHandle fromSubId(int subId); } private static final String TAG = "VvmSmsFilter"; private static final String TAG = "VvmSmsFilter"; private static final String TELEPHONY_SERVICE_PACKAGE = "com.android.phone"; private static final String TELEPHONY_SERVICE_PACKAGE = "com.android.phone"; Loading @@ -52,9 +70,37 @@ public class VisualVoicemailSmsFilter { private static Map<String, List<Pattern>> sPatterns; private static Map<String, List<Pattern>> sPatterns; private static final PhoneAccountHandleConverter DEFAULT_PHONE_ACCOUNT_HANDLE_CONVERTER = new PhoneAccountHandleConverter() { @Override public PhoneAccountHandle fromSubId(int subId) { if (!SubscriptionManager.isValidSubscriptionId(subId)) { return null; } int phoneId = SubscriptionManager.getPhoneId(subId); if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) { return null; } return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT, PhoneFactory.getPhone(phoneId).getFullIccSerialNumber()); } }; private static PhoneAccountHandleConverter sPhoneAccountHandleConverter = DEFAULT_PHONE_ACCOUNT_HANDLE_CONVERTER; /** * Wrapper to combine multiple PDU into an SMS message */ private static class FullMessage { public SmsMessage firstMessage; public String fullMessageBody; } /** /** * Attempt to parse the incoming SMS as a visual voicemail SMS. If the parsing succeeded, A * Attempt to parse the incoming SMS as a visual voicemail SMS. If the parsing succeeded, A * {@link VoicemailContract.ACTION_VOICEMAIL_SMS_RECEIVED} intent will be sent to telephony * {@link VoicemailContract#ACTION_VOICEMAIL_SMS_RECEIVED} intent will be sent to telephony * service, and the SMS will be dropped. * service, and the SMS will be dropped. * * * <p>The accepted format for a visual voicemail SMS is a generalization of the OMTP format: * <p>The accepted format for a visual voicemail SMS is a generalization of the OMTP format: Loading @@ -62,8 +108,8 @@ public class VisualVoicemailSmsFilter { * <p>[clientPrefix]:[prefix]:([key]=[value];)* * <p>[clientPrefix]:[prefix]:([key]=[value];)* * * * Additionally, if the SMS does not match the format, but matches the regex specified by the * Additionally, if the SMS does not match the format, but matches the regex specified by the * carrier in {@link com.android.internal.R.array.config_vvmSmsFilterRegexes}, the SMS will * carrier in {@link com.android.internal.R.array#config_vvmSmsFilterRegexes}, the SMS will * still be dropped and a {@link VoicemailContract.ACTION_VOICEMAIL_SMS_RECEIVED} will be sent. * still be dropped and a {@link VoicemailContract#ACTION_VOICEMAIL_SMS_RECEIVED} will be sent. * * * @return true if the SMS has been parsed to be a visual voicemail SMS and should be dropped * @return true if the SMS has been parsed to be a visual voicemail SMS and should be dropped */ */ Loading @@ -72,23 +118,24 @@ public class VisualVoicemailSmsFilter { TelephonyManager telephonyManager = TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); VisualVoicemailSmsFilterSettings settings = VisualVoicemailSmsFilterSettings settings; telephonyManager.getActiveVisualVoicemailSmsFilterSettings(subId); settings = telephonyManager.getActiveVisualVoicemailSmsFilterSettings(subId); if (settings == null) { if (settings == null) { return false; return false; } } // TODO: filter base on originating number and destination port. PhoneAccountHandle phoneAccountHandle = phoneAccountHandleFromSubId(context, subId); PhoneAccountHandle phoneAccountHandle = sPhoneAccountHandleConverter.fromSubId(subId); if (phoneAccountHandle == null) { if (phoneAccountHandle == null) { Log.e(TAG, "Unable to convert subId " + subId + " to PhoneAccountHandle"); Log.e(TAG, "Unable to convert subId " + subId + " to PhoneAccountHandle"); return false; return false; } } String messageBody = getFullMessage(pdus, format); FullMessage fullMessage = getFullMessage(pdus, format); if(messageBody == null){ if (fullMessage == null) { // Verizon WAP push SMS is not recognized by android, which has a ascii PDU. // Carrier WAP push SMS is not recognized by android, which has a ascii PDU. // Attempt to parse it. // Attempt to parse it. Log.i(TAG, "Unparsable SMS received"); Log.i(TAG, "Unparsable SMS received"); String asciiMessage = parseAsciiPduMessage(pdus); String asciiMessage = parseAsciiPduMessage(pdus); Loading @@ -101,10 +148,34 @@ public class VisualVoicemailSmsFilter { // system decide. Usually because it is not parsable it will be dropped. // system decide. Usually because it is not parsable it will be dropped. return false; return false; } } String messageBody = fullMessage.fullMessageBody; String clientPrefix = settings.clientPrefix; String clientPrefix = settings.clientPrefix; WrappedMessageData messageData = VisualVoicemailSmsParser WrappedMessageData messageData = VisualVoicemailSmsParser .parse(clientPrefix, messageBody); .parse(clientPrefix, messageBody); if (messageData != null) { if (messageData != null) { if (settings.destinationPort == VisualVoicemailSmsFilterSettings.DESTINATION_PORT_DATA_SMS) { if (destPort == -1) { // Non-data SMS is directed to the port "-1". Log.i(TAG, "SMS matching VVM format received but is not a DATA SMS"); return false; } } else if (settings.destinationPort != VisualVoicemailSmsFilterSettings.DESTINATION_PORT_ANY) { if (settings.destinationPort != destPort) { Log.i(TAG, "SMS matching VVM format received but is not directed to port " + settings.destinationPort); return false; } } if (!settings.originatingNumbers.isEmpty() && !isSmsFromNumbers(fullMessage.firstMessage, settings.originatingNumbers)) { Log.i(TAG, "SMS matching VVM format received but is not from originating numbers"); return false; } sendVvmSmsBroadcast(context, phoneAccountHandle, messageData, null); sendVvmSmsBroadcast(context, phoneAccountHandle, messageData, null); return true; return true; } } Loading @@ -128,6 +199,19 @@ public class VisualVoicemailSmsFilter { return false; return false; } } /** * override how subId is converted to PhoneAccountHandle for tests */ @VisibleForTesting public static void setPhoneAccountHandleConverterForTest( PhoneAccountHandleConverter converter) { if (converter == null) { sPhoneAccountHandleConverter = DEFAULT_PHONE_ACCOUNT_HANDLE_CONVERTER; } else { sPhoneAccountHandleConverter = converter; } } private static void buildPatternsMap(Context context) { private static void buildPatternsMap(Context context) { if (sPatterns != null) { if (sPatterns != null) { return; return; Loading Loading @@ -170,16 +254,19 @@ public class VisualVoicemailSmsFilter { * @return the message body of the SMS, or {@code null} if it can not be parsed. * @return the message body of the SMS, or {@code null} if it can not be parsed. */ */ @Nullable @Nullable private static String getFullMessage(byte[][] pdus, String format) { private static FullMessage getFullMessage(byte[][] pdus, String format) { FullMessage result = new FullMessage(); StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder(); CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); for (byte pdu[] : pdus) { for (byte pdu[] : pdus) { SmsMessage message = SmsMessage.createFromPdu(pdu, format); SmsMessage message = SmsMessage.createFromPdu(pdu, format); if (message == null) { if (message == null) { // The PDU is not recognized by android // The PDU is not recognized by android return null; return null; } } if (result.firstMessage == null) { result.firstMessage = message; } String body = message.getMessageBody(); String body = message.getMessageBody(); if (body == null && message.getUserData() != null) { if (body == null && message.getUserData() != null) { // Attempt to interpret the user data as UTF-8. UTF-8 string over data SMS using // Attempt to interpret the user data as UTF-8. UTF-8 string over data SMS using Loading @@ -198,7 +285,8 @@ public class VisualVoicemailSmsFilter { builder.append(body); builder.append(body); } } } } return builder.toString(); result.fullMessageBody = builder.toString(); return result; } } private static String parseAsciiPduMessage(byte[][] pdus) { private static String parseAsciiPduMessage(byte[][] pdus) { Loading @@ -209,16 +297,17 @@ public class VisualVoicemailSmsFilter { return builder.toString(); return builder.toString(); } } @Nullable private static boolean isSmsFromNumbers(SmsMessage message, List<String> numbers) { private static PhoneAccountHandle phoneAccountHandleFromSubId(Context context, int subId) { if (message == null) { if (!SubscriptionManager.isValidSubscriptionId(subId)) { Log.e(TAG, "Unable to create SmsMessage from PDU, cannot determine originating number"); return null; return false; } } int phoneId = SubscriptionManager.getPhoneId(subId); if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) { for (String number : numbers) { return null; if (number.equals(message.getOriginatingAddress())) { return true; } } return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT, } PhoneFactory.getPhone(phoneId).getFullIccSerialNumber()); return false; } } } } tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsFilterTest.java +131 −15 Original line number Original line Diff line number Diff line Loading @@ -16,40 +16,156 @@ package com.android.internal.telephony; package com.android.internal.telephony; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.when; import android.content.ComponentName; import android.content.Context; import android.content.Context; import android.telecom.PhoneAccountHandle; import android.telephony.TelephonyManager; import android.telephony.TelephonyManager; import android.telephony.VisualVoicemailSmsFilterSettings; import android.telephony.VisualVoicemailSmsFilterSettings; import com.android.internal.telephony.VisualVoicemailSmsFilter.PhoneAccountHandleConverter; import junit.framework.TestCase; import junit.framework.TestCase; import org.mockito.Mockito; import org.mockito.Mockito; import java.util.Arrays; /** * Unit test for {@link VisualVoicemailSmsFilter} */ public class VisualVoicemailSmsFilterTest extends TestCase { public class VisualVoicemailSmsFilterTest extends TestCase { /** /** * b/29123941 iPhone style notification SMS is neither 3GPP nor 3GPP2, but some plain text * PDU for the following message: * message. {@link android.telephony.SmsMessage.createFromPdu()} will fail to parse it and * <p>originating number: 129 * return an invalid object, causing {@link NullPointerException} on any operation if not * <p>message: //VVM:SYNC:ev=NM;id=143;c=6;t=v;s=11111111111;dt=07/03/2017 18:17 -0800;l=4 * handled. */ */ public void testUnsupportedPdu() { private static final byte[][] SYNC_PDU = {{ Context context = Mockito.mock(Context.class); (byte) 0x07, (byte) 0x91, (byte) 0x41, (byte) 0x50, (byte) 0x74, (byte) 0x02, TelephonyManager telephonyManager = Mockito.mock(TelephonyManager.class); (byte) 0x50, (byte) 0xF5, (byte) 0x44, (byte) 0x03, (byte) 0xC9, (byte) 0x21, Mockito.when(context.getSystemServiceName(TelephonyManager.class)) (byte) 0xF9, (byte) 0x00, (byte) 0x00, (byte) 0x71, (byte) 0x30, (byte) 0x70, (byte) 0x81, (byte) 0x71, (byte) 0x81, (byte) 0x2B, (byte) 0x53, (byte) 0x06, (byte) 0x05, (byte) 0x04, (byte) 0x07, (byte) 0x10, (byte) 0x01, (byte) 0x01, (byte) 0xAF, (byte) 0x97, (byte) 0xD5, (byte) 0xDA, (byte) 0xD4, (byte) 0x4D, (byte) 0xB3, (byte) 0xCE, (byte) 0xA1, (byte) 0xAE, (byte) 0x6C, (byte) 0xEF, (byte) 0x39, (byte) 0x9B, (byte) 0xBB, (byte) 0x34, (byte) 0xB9, (byte) 0x17, (byte) 0xA3, (byte) 0xCD, (byte) 0x76, (byte) 0xE3, (byte) 0x9E, (byte) 0x6D, (byte) 0x47, (byte) 0xEF, (byte) 0xD9, (byte) 0x77, (byte) 0xF3, (byte) 0x5E, (byte) 0x2C, (byte) 0x16, (byte) 0x8B, (byte) 0xC5, (byte) 0x62, (byte) 0xB1, (byte) 0x58, (byte) 0x2C, (byte) 0x16, (byte) 0xDB, (byte) 0x91, (byte) 0xE9, (byte) 0x3D, (byte) 0xD8, (byte) 0xED, (byte) 0x05, (byte) 0x9B, (byte) 0xBD, (byte) 0x64, (byte) 0xB0, (byte) 0xD8, (byte) 0x0D, (byte) 0x14, (byte) 0xC3, (byte) 0xE9, (byte) 0x62, (byte) 0x37, (byte) 0x50, (byte) 0x0B, (byte) 0x86, (byte) 0x83, (byte) 0xC1, (byte) 0x76, (byte) 0xEC, (byte) 0x1E, (byte) 0x0D}}; private Context mContext; private TelephonyManager mTelephonyManager; @Override public void setUp() throws Exception { super.setUp(); mContext = Mockito.mock(Context.class); mTelephonyManager = Mockito.mock(TelephonyManager.class); when(mContext.getSystemServiceName(TelephonyManager.class)) .thenReturn(Context.TELEPHONY_SERVICE); .thenReturn(Context.TELEPHONY_SERVICE); Mockito.when(context.getSystemService(Mockito.anyString())).thenReturn(telephonyManager); when(mContext.getSystemService(Context.TELEPHONY_SERVICE)) .thenReturn(mTelephonyManager); VisualVoicemailSmsFilter.setPhoneAccountHandleConverterForTest( new PhoneAccountHandleConverter() { @Override public PhoneAccountHandle fromSubId(int subId) { return new PhoneAccountHandle( new ComponentName("com.android.internal.telephony", "VisualVoicemailSmsFilterTest"), "foo"); } }); } VisualVoicemailSmsFilterSettings settings = new VisualVoicemailSmsFilterSettings.Builder() @Override .build(); public void tearDown() throws Exception { VisualVoicemailSmsFilter.setPhoneAccountHandleConverterForTest(null); super.tearDown(); } Mockito.when(telephonyManager .getVisualVoicemailSmsFilterSettings(Mockito.anyInt())) /** .thenReturn(settings); * Notification SMS targeting over devices do not follow 3GPP or 3GPP2 standards, but instead * use a plain text message. {@link android.telephony.SmsMessage#createFromPdu(byte[], String)} * will fail to parse it and return an invalid object, causing {@link NullPointerException} on * any operation if not handled. */ public void testUnsupportedPdu() { setSettings(new VisualVoicemailSmsFilterSettings.Builder().build()); byte[][] pdus = { byte[][] pdus = { ("MBOXUPDATE?m=11;server=example.com;" ("MBOXUPDATE?m=11;server=example.com;" + "port=143;name=1234567890@example.com;pw=CphQJKnYS4jEiDO").getBytes()}; + "port=143;name=1234567890@example.com;pw=CphQJKnYS4jEiDO").getBytes()}; VisualVoicemailSmsFilter.filter(context, pdus, SmsConstants.FORMAT_3GPP2, 0, 0); assertFalse( VisualVoicemailSmsFilter.filter(mContext, pdus, SmsConstants.FORMAT_3GPP, 0, 0)); } } public void testOriginatingNumber_unspecified_filtered() { setSettings(new VisualVoicemailSmsFilterSettings.Builder().build()); assertTrue(VisualVoicemailSmsFilter .filter(mContext, SYNC_PDU, SmsConstants.FORMAT_3GPP, 0, 0)); } public void testOriginatingNumber_match_filtered() { setSettings( new VisualVoicemailSmsFilterSettings.Builder().setOriginatingNumbers( Arrays.asList("129") ).build()); assertTrue(VisualVoicemailSmsFilter .filter(mContext, SYNC_PDU, SmsConstants.FORMAT_3GPP, 0, 0)); } public void testOriginatingNumber_mismatch_notFiltered() { setSettings( new VisualVoicemailSmsFilterSettings.Builder().setOriginatingNumbers( Arrays.asList("128") ).build()); assertFalse(VisualVoicemailSmsFilter .filter(mContext, SYNC_PDU, SmsConstants.FORMAT_3GPP, 0, 0)); } public void testDestinationPort_anyMatch_filtered() { setSettings(new VisualVoicemailSmsFilterSettings.Builder() .setDestinationPort(123).build()); assertTrue(VisualVoicemailSmsFilter .filter(mContext, SYNC_PDU, SmsConstants.FORMAT_3GPP, 123, 0)); } public void testDestinationPort_anyData_filtered() { setSettings(new VisualVoicemailSmsFilterSettings.Builder() .setDestinationPort(VisualVoicemailSmsFilterSettings.DESTINATION_PORT_DATA_SMS) .build()); assertTrue(VisualVoicemailSmsFilter .filter(mContext, SYNC_PDU, SmsConstants.FORMAT_3GPP, 456, 0)); } public void testDestinationPort_anyData_textReceived_notFiltered() { setSettings(new VisualVoicemailSmsFilterSettings.Builder() .setDestinationPort(VisualVoicemailSmsFilterSettings.DESTINATION_PORT_DATA_SMS) .build()); assertFalse(VisualVoicemailSmsFilter .filter(mContext, SYNC_PDU, SmsConstants.FORMAT_3GPP, -1, 0)); } public void testDestinationPort_mismatch_notFiltered() { setSettings(new VisualVoicemailSmsFilterSettings.Builder() .setDestinationPort(123).build()); assertFalse(VisualVoicemailSmsFilter .filter(mContext, SYNC_PDU, SmsConstants.FORMAT_3GPP, 456, 0)); } private void setSettings(VisualVoicemailSmsFilterSettings settings) { when(mTelephonyManager.getActiveVisualVoicemailSmsFilterSettings(anyInt())) .thenReturn(settings); } } } Loading
src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java +116 −27 Original line number Original line Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.telephony.VisualVoicemailSmsFilterSettings; import android.util.ArrayMap; import android.util.ArrayMap; import android.util.Log; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.VisualVoicemailSmsParser.WrappedMessageData; import com.android.internal.telephony.VisualVoicemailSmsParser.WrappedMessageData; import java.nio.ByteBuffer; import java.nio.ByteBuffer; Loading @@ -40,8 +41,25 @@ import java.util.List; import java.util.Map; import java.util.Map; import java.util.regex.Pattern; import java.util.regex.Pattern; /** * Filters SMS to {@link android.telephony.VisualVoicemailService}, based on the config from {@link * VisualVoicemailSmsFilterSettings}. The SMS is sent to telephony service which will do the actual * dispatching. */ public class VisualVoicemailSmsFilter { public class VisualVoicemailSmsFilter { /** * Interface to convert subIds so the logic can be replaced in tests. */ @VisibleForTesting public interface PhoneAccountHandleConverter { /** * Convert the subId to a {@link PhoneAccountHandle} */ PhoneAccountHandle fromSubId(int subId); } private static final String TAG = "VvmSmsFilter"; private static final String TAG = "VvmSmsFilter"; private static final String TELEPHONY_SERVICE_PACKAGE = "com.android.phone"; private static final String TELEPHONY_SERVICE_PACKAGE = "com.android.phone"; Loading @@ -52,9 +70,37 @@ public class VisualVoicemailSmsFilter { private static Map<String, List<Pattern>> sPatterns; private static Map<String, List<Pattern>> sPatterns; private static final PhoneAccountHandleConverter DEFAULT_PHONE_ACCOUNT_HANDLE_CONVERTER = new PhoneAccountHandleConverter() { @Override public PhoneAccountHandle fromSubId(int subId) { if (!SubscriptionManager.isValidSubscriptionId(subId)) { return null; } int phoneId = SubscriptionManager.getPhoneId(subId); if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) { return null; } return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT, PhoneFactory.getPhone(phoneId).getFullIccSerialNumber()); } }; private static PhoneAccountHandleConverter sPhoneAccountHandleConverter = DEFAULT_PHONE_ACCOUNT_HANDLE_CONVERTER; /** * Wrapper to combine multiple PDU into an SMS message */ private static class FullMessage { public SmsMessage firstMessage; public String fullMessageBody; } /** /** * Attempt to parse the incoming SMS as a visual voicemail SMS. If the parsing succeeded, A * Attempt to parse the incoming SMS as a visual voicemail SMS. If the parsing succeeded, A * {@link VoicemailContract.ACTION_VOICEMAIL_SMS_RECEIVED} intent will be sent to telephony * {@link VoicemailContract#ACTION_VOICEMAIL_SMS_RECEIVED} intent will be sent to telephony * service, and the SMS will be dropped. * service, and the SMS will be dropped. * * * <p>The accepted format for a visual voicemail SMS is a generalization of the OMTP format: * <p>The accepted format for a visual voicemail SMS is a generalization of the OMTP format: Loading @@ -62,8 +108,8 @@ public class VisualVoicemailSmsFilter { * <p>[clientPrefix]:[prefix]:([key]=[value];)* * <p>[clientPrefix]:[prefix]:([key]=[value];)* * * * Additionally, if the SMS does not match the format, but matches the regex specified by the * Additionally, if the SMS does not match the format, but matches the regex specified by the * carrier in {@link com.android.internal.R.array.config_vvmSmsFilterRegexes}, the SMS will * carrier in {@link com.android.internal.R.array#config_vvmSmsFilterRegexes}, the SMS will * still be dropped and a {@link VoicemailContract.ACTION_VOICEMAIL_SMS_RECEIVED} will be sent. * still be dropped and a {@link VoicemailContract#ACTION_VOICEMAIL_SMS_RECEIVED} will be sent. * * * @return true if the SMS has been parsed to be a visual voicemail SMS and should be dropped * @return true if the SMS has been parsed to be a visual voicemail SMS and should be dropped */ */ Loading @@ -72,23 +118,24 @@ public class VisualVoicemailSmsFilter { TelephonyManager telephonyManager = TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); VisualVoicemailSmsFilterSettings settings = VisualVoicemailSmsFilterSettings settings; telephonyManager.getActiveVisualVoicemailSmsFilterSettings(subId); settings = telephonyManager.getActiveVisualVoicemailSmsFilterSettings(subId); if (settings == null) { if (settings == null) { return false; return false; } } // TODO: filter base on originating number and destination port. PhoneAccountHandle phoneAccountHandle = phoneAccountHandleFromSubId(context, subId); PhoneAccountHandle phoneAccountHandle = sPhoneAccountHandleConverter.fromSubId(subId); if (phoneAccountHandle == null) { if (phoneAccountHandle == null) { Log.e(TAG, "Unable to convert subId " + subId + " to PhoneAccountHandle"); Log.e(TAG, "Unable to convert subId " + subId + " to PhoneAccountHandle"); return false; return false; } } String messageBody = getFullMessage(pdus, format); FullMessage fullMessage = getFullMessage(pdus, format); if(messageBody == null){ if (fullMessage == null) { // Verizon WAP push SMS is not recognized by android, which has a ascii PDU. // Carrier WAP push SMS is not recognized by android, which has a ascii PDU. // Attempt to parse it. // Attempt to parse it. Log.i(TAG, "Unparsable SMS received"); Log.i(TAG, "Unparsable SMS received"); String asciiMessage = parseAsciiPduMessage(pdus); String asciiMessage = parseAsciiPduMessage(pdus); Loading @@ -101,10 +148,34 @@ public class VisualVoicemailSmsFilter { // system decide. Usually because it is not parsable it will be dropped. // system decide. Usually because it is not parsable it will be dropped. return false; return false; } } String messageBody = fullMessage.fullMessageBody; String clientPrefix = settings.clientPrefix; String clientPrefix = settings.clientPrefix; WrappedMessageData messageData = VisualVoicemailSmsParser WrappedMessageData messageData = VisualVoicemailSmsParser .parse(clientPrefix, messageBody); .parse(clientPrefix, messageBody); if (messageData != null) { if (messageData != null) { if (settings.destinationPort == VisualVoicemailSmsFilterSettings.DESTINATION_PORT_DATA_SMS) { if (destPort == -1) { // Non-data SMS is directed to the port "-1". Log.i(TAG, "SMS matching VVM format received but is not a DATA SMS"); return false; } } else if (settings.destinationPort != VisualVoicemailSmsFilterSettings.DESTINATION_PORT_ANY) { if (settings.destinationPort != destPort) { Log.i(TAG, "SMS matching VVM format received but is not directed to port " + settings.destinationPort); return false; } } if (!settings.originatingNumbers.isEmpty() && !isSmsFromNumbers(fullMessage.firstMessage, settings.originatingNumbers)) { Log.i(TAG, "SMS matching VVM format received but is not from originating numbers"); return false; } sendVvmSmsBroadcast(context, phoneAccountHandle, messageData, null); sendVvmSmsBroadcast(context, phoneAccountHandle, messageData, null); return true; return true; } } Loading @@ -128,6 +199,19 @@ public class VisualVoicemailSmsFilter { return false; return false; } } /** * override how subId is converted to PhoneAccountHandle for tests */ @VisibleForTesting public static void setPhoneAccountHandleConverterForTest( PhoneAccountHandleConverter converter) { if (converter == null) { sPhoneAccountHandleConverter = DEFAULT_PHONE_ACCOUNT_HANDLE_CONVERTER; } else { sPhoneAccountHandleConverter = converter; } } private static void buildPatternsMap(Context context) { private static void buildPatternsMap(Context context) { if (sPatterns != null) { if (sPatterns != null) { return; return; Loading Loading @@ -170,16 +254,19 @@ public class VisualVoicemailSmsFilter { * @return the message body of the SMS, or {@code null} if it can not be parsed. * @return the message body of the SMS, or {@code null} if it can not be parsed. */ */ @Nullable @Nullable private static String getFullMessage(byte[][] pdus, String format) { private static FullMessage getFullMessage(byte[][] pdus, String format) { FullMessage result = new FullMessage(); StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder(); CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); for (byte pdu[] : pdus) { for (byte pdu[] : pdus) { SmsMessage message = SmsMessage.createFromPdu(pdu, format); SmsMessage message = SmsMessage.createFromPdu(pdu, format); if (message == null) { if (message == null) { // The PDU is not recognized by android // The PDU is not recognized by android return null; return null; } } if (result.firstMessage == null) { result.firstMessage = message; } String body = message.getMessageBody(); String body = message.getMessageBody(); if (body == null && message.getUserData() != null) { if (body == null && message.getUserData() != null) { // Attempt to interpret the user data as UTF-8. UTF-8 string over data SMS using // Attempt to interpret the user data as UTF-8. UTF-8 string over data SMS using Loading @@ -198,7 +285,8 @@ public class VisualVoicemailSmsFilter { builder.append(body); builder.append(body); } } } } return builder.toString(); result.fullMessageBody = builder.toString(); return result; } } private static String parseAsciiPduMessage(byte[][] pdus) { private static String parseAsciiPduMessage(byte[][] pdus) { Loading @@ -209,16 +297,17 @@ public class VisualVoicemailSmsFilter { return builder.toString(); return builder.toString(); } } @Nullable private static boolean isSmsFromNumbers(SmsMessage message, List<String> numbers) { private static PhoneAccountHandle phoneAccountHandleFromSubId(Context context, int subId) { if (message == null) { if (!SubscriptionManager.isValidSubscriptionId(subId)) { Log.e(TAG, "Unable to create SmsMessage from PDU, cannot determine originating number"); return null; return false; } } int phoneId = SubscriptionManager.getPhoneId(subId); if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) { for (String number : numbers) { return null; if (number.equals(message.getOriginatingAddress())) { return true; } } return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT, } PhoneFactory.getPhone(phoneId).getFullIccSerialNumber()); return false; } } } }
tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsFilterTest.java +131 −15 Original line number Original line Diff line number Diff line Loading @@ -16,40 +16,156 @@ package com.android.internal.telephony; package com.android.internal.telephony; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.when; import android.content.ComponentName; import android.content.Context; import android.content.Context; import android.telecom.PhoneAccountHandle; import android.telephony.TelephonyManager; import android.telephony.TelephonyManager; import android.telephony.VisualVoicemailSmsFilterSettings; import android.telephony.VisualVoicemailSmsFilterSettings; import com.android.internal.telephony.VisualVoicemailSmsFilter.PhoneAccountHandleConverter; import junit.framework.TestCase; import junit.framework.TestCase; import org.mockito.Mockito; import org.mockito.Mockito; import java.util.Arrays; /** * Unit test for {@link VisualVoicemailSmsFilter} */ public class VisualVoicemailSmsFilterTest extends TestCase { public class VisualVoicemailSmsFilterTest extends TestCase { /** /** * b/29123941 iPhone style notification SMS is neither 3GPP nor 3GPP2, but some plain text * PDU for the following message: * message. {@link android.telephony.SmsMessage.createFromPdu()} will fail to parse it and * <p>originating number: 129 * return an invalid object, causing {@link NullPointerException} on any operation if not * <p>message: //VVM:SYNC:ev=NM;id=143;c=6;t=v;s=11111111111;dt=07/03/2017 18:17 -0800;l=4 * handled. */ */ public void testUnsupportedPdu() { private static final byte[][] SYNC_PDU = {{ Context context = Mockito.mock(Context.class); (byte) 0x07, (byte) 0x91, (byte) 0x41, (byte) 0x50, (byte) 0x74, (byte) 0x02, TelephonyManager telephonyManager = Mockito.mock(TelephonyManager.class); (byte) 0x50, (byte) 0xF5, (byte) 0x44, (byte) 0x03, (byte) 0xC9, (byte) 0x21, Mockito.when(context.getSystemServiceName(TelephonyManager.class)) (byte) 0xF9, (byte) 0x00, (byte) 0x00, (byte) 0x71, (byte) 0x30, (byte) 0x70, (byte) 0x81, (byte) 0x71, (byte) 0x81, (byte) 0x2B, (byte) 0x53, (byte) 0x06, (byte) 0x05, (byte) 0x04, (byte) 0x07, (byte) 0x10, (byte) 0x01, (byte) 0x01, (byte) 0xAF, (byte) 0x97, (byte) 0xD5, (byte) 0xDA, (byte) 0xD4, (byte) 0x4D, (byte) 0xB3, (byte) 0xCE, (byte) 0xA1, (byte) 0xAE, (byte) 0x6C, (byte) 0xEF, (byte) 0x39, (byte) 0x9B, (byte) 0xBB, (byte) 0x34, (byte) 0xB9, (byte) 0x17, (byte) 0xA3, (byte) 0xCD, (byte) 0x76, (byte) 0xE3, (byte) 0x9E, (byte) 0x6D, (byte) 0x47, (byte) 0xEF, (byte) 0xD9, (byte) 0x77, (byte) 0xF3, (byte) 0x5E, (byte) 0x2C, (byte) 0x16, (byte) 0x8B, (byte) 0xC5, (byte) 0x62, (byte) 0xB1, (byte) 0x58, (byte) 0x2C, (byte) 0x16, (byte) 0xDB, (byte) 0x91, (byte) 0xE9, (byte) 0x3D, (byte) 0xD8, (byte) 0xED, (byte) 0x05, (byte) 0x9B, (byte) 0xBD, (byte) 0x64, (byte) 0xB0, (byte) 0xD8, (byte) 0x0D, (byte) 0x14, (byte) 0xC3, (byte) 0xE9, (byte) 0x62, (byte) 0x37, (byte) 0x50, (byte) 0x0B, (byte) 0x86, (byte) 0x83, (byte) 0xC1, (byte) 0x76, (byte) 0xEC, (byte) 0x1E, (byte) 0x0D}}; private Context mContext; private TelephonyManager mTelephonyManager; @Override public void setUp() throws Exception { super.setUp(); mContext = Mockito.mock(Context.class); mTelephonyManager = Mockito.mock(TelephonyManager.class); when(mContext.getSystemServiceName(TelephonyManager.class)) .thenReturn(Context.TELEPHONY_SERVICE); .thenReturn(Context.TELEPHONY_SERVICE); Mockito.when(context.getSystemService(Mockito.anyString())).thenReturn(telephonyManager); when(mContext.getSystemService(Context.TELEPHONY_SERVICE)) .thenReturn(mTelephonyManager); VisualVoicemailSmsFilter.setPhoneAccountHandleConverterForTest( new PhoneAccountHandleConverter() { @Override public PhoneAccountHandle fromSubId(int subId) { return new PhoneAccountHandle( new ComponentName("com.android.internal.telephony", "VisualVoicemailSmsFilterTest"), "foo"); } }); } VisualVoicemailSmsFilterSettings settings = new VisualVoicemailSmsFilterSettings.Builder() @Override .build(); public void tearDown() throws Exception { VisualVoicemailSmsFilter.setPhoneAccountHandleConverterForTest(null); super.tearDown(); } Mockito.when(telephonyManager .getVisualVoicemailSmsFilterSettings(Mockito.anyInt())) /** .thenReturn(settings); * Notification SMS targeting over devices do not follow 3GPP or 3GPP2 standards, but instead * use a plain text message. {@link android.telephony.SmsMessage#createFromPdu(byte[], String)} * will fail to parse it and return an invalid object, causing {@link NullPointerException} on * any operation if not handled. */ public void testUnsupportedPdu() { setSettings(new VisualVoicemailSmsFilterSettings.Builder().build()); byte[][] pdus = { byte[][] pdus = { ("MBOXUPDATE?m=11;server=example.com;" ("MBOXUPDATE?m=11;server=example.com;" + "port=143;name=1234567890@example.com;pw=CphQJKnYS4jEiDO").getBytes()}; + "port=143;name=1234567890@example.com;pw=CphQJKnYS4jEiDO").getBytes()}; VisualVoicemailSmsFilter.filter(context, pdus, SmsConstants.FORMAT_3GPP2, 0, 0); assertFalse( VisualVoicemailSmsFilter.filter(mContext, pdus, SmsConstants.FORMAT_3GPP, 0, 0)); } } public void testOriginatingNumber_unspecified_filtered() { setSettings(new VisualVoicemailSmsFilterSettings.Builder().build()); assertTrue(VisualVoicemailSmsFilter .filter(mContext, SYNC_PDU, SmsConstants.FORMAT_3GPP, 0, 0)); } public void testOriginatingNumber_match_filtered() { setSettings( new VisualVoicemailSmsFilterSettings.Builder().setOriginatingNumbers( Arrays.asList("129") ).build()); assertTrue(VisualVoicemailSmsFilter .filter(mContext, SYNC_PDU, SmsConstants.FORMAT_3GPP, 0, 0)); } public void testOriginatingNumber_mismatch_notFiltered() { setSettings( new VisualVoicemailSmsFilterSettings.Builder().setOriginatingNumbers( Arrays.asList("128") ).build()); assertFalse(VisualVoicemailSmsFilter .filter(mContext, SYNC_PDU, SmsConstants.FORMAT_3GPP, 0, 0)); } public void testDestinationPort_anyMatch_filtered() { setSettings(new VisualVoicemailSmsFilterSettings.Builder() .setDestinationPort(123).build()); assertTrue(VisualVoicemailSmsFilter .filter(mContext, SYNC_PDU, SmsConstants.FORMAT_3GPP, 123, 0)); } public void testDestinationPort_anyData_filtered() { setSettings(new VisualVoicemailSmsFilterSettings.Builder() .setDestinationPort(VisualVoicemailSmsFilterSettings.DESTINATION_PORT_DATA_SMS) .build()); assertTrue(VisualVoicemailSmsFilter .filter(mContext, SYNC_PDU, SmsConstants.FORMAT_3GPP, 456, 0)); } public void testDestinationPort_anyData_textReceived_notFiltered() { setSettings(new VisualVoicemailSmsFilterSettings.Builder() .setDestinationPort(VisualVoicemailSmsFilterSettings.DESTINATION_PORT_DATA_SMS) .build()); assertFalse(VisualVoicemailSmsFilter .filter(mContext, SYNC_PDU, SmsConstants.FORMAT_3GPP, -1, 0)); } public void testDestinationPort_mismatch_notFiltered() { setSettings(new VisualVoicemailSmsFilterSettings.Builder() .setDestinationPort(123).build()); assertFalse(VisualVoicemailSmsFilter .filter(mContext, SYNC_PDU, SmsConstants.FORMAT_3GPP, 456, 0)); } private void setSettings(VisualVoicemailSmsFilterSettings settings) { when(mTelephonyManager.getActiveVisualVoicemailSmsFilterSettings(anyInt())) .thenReturn(settings); } } }