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

Commit d90bd831 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Allow port and originating number to be specified for VVM SMS" into oc-dev

parents bb6b3d81 a6db1559
Loading
Loading
Loading
Loading
+116 −27
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import android.telephony.VisualVoicemailSmsFilterSettings;
import android.util.ArrayMap;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.VisualVoicemailSmsParser.WrappedMessageData;

import java.nio.ByteBuffer;
@@ -40,8 +41,25 @@ import java.util.List;
import java.util.Map;
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 {

    /**
     * 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 TELEPHONY_SERVICE_PACKAGE = "com.android.phone";
@@ -52,9 +70,37 @@ public class VisualVoicemailSmsFilter {

    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
     * {@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.
     *
     * <p>The accepted format for a visual voicemail SMS is a generalization of the OMTP format:
@@ -62,8 +108,8 @@ public class VisualVoicemailSmsFilter {
     * <p>[clientPrefix]:[prefix]:([key]=[value];)*
     *
     * 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
     * still be dropped and a {@link VoicemailContract.ACTION_VOICEMAIL_SMS_RECEIVED} will be sent.
     * 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.
     *
     * @return true if the SMS has been parsed to be a visual voicemail SMS and should be dropped
     */
@@ -72,23 +118,24 @@ public class VisualVoicemailSmsFilter {
        TelephonyManager telephonyManager =
                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);

        VisualVoicemailSmsFilterSettings settings =
                telephonyManager.getActiveVisualVoicemailSmsFilterSettings(subId);
        VisualVoicemailSmsFilterSettings settings;
        settings = telephonyManager.getActiveVisualVoicemailSmsFilterSettings(subId);

        if (settings == null) {
            return false;
        }
        // TODO: filter base on originating number and destination port.

        PhoneAccountHandle phoneAccountHandle = phoneAccountHandleFromSubId(context, subId);
        PhoneAccountHandle phoneAccountHandle = sPhoneAccountHandleConverter.fromSubId(subId);

        if (phoneAccountHandle == null) {
            Log.e(TAG, "Unable to convert subId " + subId + " to PhoneAccountHandle");
            return false;
        }

        String messageBody = getFullMessage(pdus, format);
        FullMessage fullMessage = getFullMessage(pdus, format);

        if(messageBody == null){
            // Verizon WAP push SMS is not recognized by android, which has a ascii PDU.
        if (fullMessage == null) {
            // Carrier WAP push SMS is not recognized by android, which has a ascii PDU.
            // Attempt to parse it.
            Log.i(TAG, "Unparsable SMS received");
            String asciiMessage = parseAsciiPduMessage(pdus);
@@ -101,10 +148,34 @@ public class VisualVoicemailSmsFilter {
            // system decide. Usually because it is not parsable it will be dropped.
            return false;
        }

        String messageBody = fullMessage.fullMessageBody;
        String clientPrefix = settings.clientPrefix;
        WrappedMessageData messageData = VisualVoicemailSmsParser
                .parse(clientPrefix, messageBody);
        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);
            return true;
        }
@@ -128,6 +199,19 @@ public class VisualVoicemailSmsFilter {
        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) {
        if (sPatterns != null) {
            return;
@@ -170,16 +254,19 @@ public class VisualVoicemailSmsFilter {
     * @return the message body of the SMS, or {@code null} if it can not be parsed.
     */
    @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();
        CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
        for (byte pdu[] : pdus) {
            SmsMessage message = SmsMessage.createFromPdu(pdu, format);

            if (message == null) {
                // The PDU is not recognized by android
                return null;
            }
            if (result.firstMessage == null) {
                result.firstMessage = message;
            }
            String body = message.getMessageBody();
            if (body == null && message.getUserData() != null) {
                // Attempt to interpret the user data as UTF-8. UTF-8 string over data SMS using
@@ -198,7 +285,8 @@ public class VisualVoicemailSmsFilter {
                builder.append(body);
            }
        }
        return builder.toString();
        result.fullMessageBody = builder.toString();
        return result;
    }

    private static String parseAsciiPduMessage(byte[][] pdus) {
@@ -209,16 +297,17 @@ public class VisualVoicemailSmsFilter {
        return builder.toString();
    }

    @Nullable
    private static PhoneAccountHandle phoneAccountHandleFromSubId(Context context, int subId) {
        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
            return null;
    private static boolean isSmsFromNumbers(SmsMessage message, List<String> numbers) {
        if (message == null) {
            Log.e(TAG, "Unable to create SmsMessage from PDU, cannot determine originating number");
            return false;
        }
        int phoneId = SubscriptionManager.getPhoneId(subId);
        if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) {
            return null;

        for (String number : numbers) {
            if (number.equals(message.getOriginatingAddress())) {
                return true;
            }
        return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT,
                PhoneFactory.getPhone(phoneId).getFullIccSerialNumber());
        }
        return false;
    }
}
+131 −15
Original line number Diff line number Diff line
@@ -16,40 +16,156 @@

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.telecom.PhoneAccountHandle;
import android.telephony.TelephonyManager;
import android.telephony.VisualVoicemailSmsFilterSettings;

import com.android.internal.telephony.VisualVoicemailSmsFilter.PhoneAccountHandleConverter;

import junit.framework.TestCase;

import org.mockito.Mockito;

import java.util.Arrays;

/**
 * Unit test for {@link VisualVoicemailSmsFilter}
 */
public class VisualVoicemailSmsFilterTest extends TestCase {

    /**
     * b/29123941 iPhone style notification SMS is neither 3GPP nor 3GPP2, but some plain text
     * message. {@link android.telephony.SmsMessage.createFromPdu()} will fail to parse it and
     * return an invalid object, causing {@link NullPointerException} on any operation if not
     * handled.
     * PDU for the following message:
     * <p>originating number: 129
     * <p>message: //VVM:SYNC:ev=NM;id=143;c=6;t=v;s=11111111111;dt=07/03/2017 18:17 -0800;l=4
     */
    public void testUnsupportedPdu() {
        Context context = Mockito.mock(Context.class);
        TelephonyManager telephonyManager = Mockito.mock(TelephonyManager.class);
        Mockito.when(context.getSystemServiceName(TelephonyManager.class))
    private static final byte[][] SYNC_PDU = {{
            (byte) 0x07, (byte) 0x91, (byte) 0x41, (byte) 0x50, (byte) 0x74, (byte) 0x02,
            (byte) 0x50, (byte) 0xF5, (byte) 0x44, (byte) 0x03, (byte) 0xC9, (byte) 0x21,
            (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);
        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()
                .build();
    @Override
    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 = {
                ("MBOXUPDATE?m=11;server=example.com;"
                        + "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);
    }
}