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 Original line Diff line number Diff line
@@ -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;
@@ -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";
@@ -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:
@@ -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
     */
     */
@@ -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);
@@ -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;
        }
        }
@@ -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;
@@ -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
@@ -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) {
@@ -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;
    }
    }
}
}
+131 −15
Original line number Original line Diff line number Diff line
@@ -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);
    }
}
}