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

Commit 9452da00 authored by Ta-wei Yen's avatar Ta-wei Yen
Browse files

Handle Verizon iPhone WAP push for VVM

Verizon iPhone WAP push is a pure ASCII message, which is used to be
dropped by the system.

this CL will attempt to parse such SMS as an "alternative" VVM format.
The filter will not drop the SMS.

Change-Id: I7ea68bfa7a0bdc190fdc86c85c0e532cbf301e74
Fixes: 30123702
parent 2014a27a
Loading
Loading
Loading
Loading
+44 −22
Original line number Diff line number Diff line
@@ -15,19 +15,18 @@
 */
package com.android.internal.telephony;

import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.provider.VoicemailContract;
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.telephony.VisualVoicemailSmsFilterSettings;
import android.util.Log;

import android.telephony.SmsMessage;

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

import java.nio.charset.StandardCharsets;

public class VisualVoicemailSmsFilter {

    private static final String TAG = "VvmSmsFilter";
@@ -61,13 +60,34 @@ public class VisualVoicemailSmsFilter {
        // TODO: filter base on originating number and destination port.

        String messageBody = getFullMessage(pdus, format);

        if(messageBody == null){
            return false;
            // Verizon 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);
            WrappedMessageData messageData = VisualVoicemailSmsParser
                .parseAlternativeFormat(asciiMessage);
            if (messageData != null) {
                sendVvmSmsBroadcast(context, vvmClientPackage, subId, messageData);
            }
            // Confidence for what the message actually is is low. Don't remove the message and let
            // system decide. Usually because it is not parsable it will be dropped.
            return false;
        } else {
            String clientPrefix = settings.clientPrefix;

        WrappedMessageData messageData = VisualVoicemailSmsParser.parse(clientPrefix, messageBody);
            WrappedMessageData messageData = VisualVoicemailSmsParser
                .parse(clientPrefix, messageBody);
            if (messageData != null) {
                sendVvmSmsBroadcast(context, vvmClientPackage, subId, messageData);
                return true;
            }
        }
        return false;
    }

    private static void sendVvmSmsBroadcast(Context context, String vvmClientPackage, int subId,
        WrappedMessageData messageData) {
        Log.i(TAG, "VVM SMS received");
        Intent intent = new Intent(VoicemailContract.ACTION_VOICEMAIL_SMS_RECEIVED);
        intent.putExtra(VoicemailContract.EXTRA_VOICEMAIL_SMS_PREFIX, messageData.prefix);
@@ -75,10 +95,6 @@ public class VisualVoicemailSmsFilter {
        intent.putExtra(VoicemailContract.EXTRA_VOICEMAIL_SMS_SUBID, subId);
        intent.setPackage(vvmClientPackage);
        context.sendBroadcast(intent);
            return true;
        }

        return false;
    }

    private static String getFullMessage(byte[][] pdus, String format) {
@@ -86,10 +102,8 @@ public class VisualVoicemailSmsFilter {
        for (byte pdu[] : pdus) {
            SmsMessage message =SmsMessage.createFromPdu(pdu, format);

            if(message == null || message.mWrappedSmsMessage == null) {
                // b/29123941 Certain PDU will cause createFromPdu() to return a SmsMessage with
                // null mWrappedSmsMessage, throwing NPE on any method called. In this case, just
                // ignore the message.
            if (message == null) {
                // The PDU is not recognized by android
                return null;
            }
            String body = message.getMessageBody();
@@ -99,4 +113,12 @@ public class VisualVoicemailSmsFilter {
        }
        return builder.toString();
    }

    private static String parseAsciiPduMessage(byte[][] pdus) {
        StringBuilder builder = new StringBuilder();
        for (byte pdu[] : pdus) {
            builder.append(new String(pdu, StandardCharsets.US_ASCII));
        }
        return builder.toString();
    }
}
+43 −0
Original line number Diff line number Diff line
@@ -20,6 +20,10 @@ import android.os.Bundle;

public class VisualVoicemailSmsParser {

    private static final String[] ALLOWED_ALTERNATIVE_FORMAT_EVENT = new String[] {
            "MBOXUPDATE", "UNRECOGNIZED"
    };

    /**
     * Class wrapping the raw OMTP message data, internally represented as as map of all key-value
     * pairs found in the SMS body. <p> All the methods return null if either the field was not
@@ -80,6 +84,7 @@ public class VisualVoicemailSmsParser {
     * @param message The sms string with the prefix removed.
     * @return A WrappedMessageData object containing the map.
     */
    @Nullable
    private static Bundle parseSmsBody(String message) {
        // TODO: ensure fail if format does not match
        Bundle keyValues = new Bundle();
@@ -107,4 +112,42 @@ public class VisualVoicemailSmsParser {

        return keyValues;
    }

    /**
     * The alternative format is [Event]?([key]=[value])*, for example
     *
     * <p>"MBOXUPDATE?m=1;server=example.com;port=143;name=foo@example.com;pw=foo".
     *
     * <p>This format is not protected with a client prefix and should be handled with care. For
     * safety, the event type must be one of {@link #ALLOWED_ALTERNATIVE_FORMAT_EVENT}
     */
    @Nullable
    public static WrappedMessageData parseAlternativeFormat(String smsBody) {
        try {
            int eventTypeEnd = smsBody.indexOf("?");
            if (eventTypeEnd == -1) {
                return null;
            }
            String eventType = smsBody.substring(0, eventTypeEnd);
            if (!isAllowedAlternativeFormatEvent(eventType)) {
                return null;
            }
            Bundle fields = parseSmsBody(smsBody.substring(eventTypeEnd + 1));
            if (fields == null) {
                return null;
            }
            return new WrappedMessageData(eventType, fields);
        } catch (IndexOutOfBoundsException e) {
            return null;
        }
    }

    private static boolean isAllowedAlternativeFormatEvent(String eventType) {
        for (String event : ALLOWED_ALTERNATIVE_FORMAT_EVENT) {
            if (event.equals(eventType)) {
                return true;
            }
        }
        return false;
    }
}
+38 −2
Original line number Diff line number Diff line
@@ -16,9 +16,7 @@
package com.android.internal.telephony;

import android.test.suitebuilder.annotation.SmallTest;

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

import junit.framework.TestCase;

public class VisualVoicemailSmsParserTest extends TestCase {
@@ -147,4 +145,42 @@ public class VisualVoicemailSmsParserTest extends TestCase {
        assertEquals("STATUS", result.prefix);
        assertEquals("", result.fields.getString("key"));
    }

    @SmallTest
    public void testAlternativeParsing_Mboxupdate() {
        WrappedMessageData result = VisualVoicemailSmsParser.parseAlternativeFormat(
            "MBOXUPDATE?m=1;server=example.com;port=143;name=foo@example.com;pw=bar");

        assertEquals("MBOXUPDATE", result.prefix);
        assertEquals("1", result.fields.getString("m"));
        assertEquals("example.com", result.fields.getString("server"));
        assertEquals("143", result.fields.getString("port"));
        assertEquals("foo@example.com", result.fields.getString("name"));
        assertEquals("bar", result.fields.getString("pw"));
    }

    @SmallTest
    public void testAlternativeParsing_Unrecognized() {
        WrappedMessageData result = VisualVoicemailSmsParser.parseAlternativeFormat(
            "UNRECOGNIZED?cmd=STATUS");

        assertEquals("UNRECOGNIZED", result.prefix);
        assertEquals("STATUS", result.fields.getString("cmd"));
    }

    @SmallTest
    public void testAlternativeParsingFail_MissingSeparator() {
        WrappedMessageData result = VisualVoicemailSmsParser.parseAlternativeFormat(
            "I send SMS in weird formats");

        assertNull(result);
    }

    @SmallTest
    public void testAlternativeParsingFail_NotWhitelistedEvent() {
        WrappedMessageData result = VisualVoicemailSmsParser.parseAlternativeFormat(
            "AreYouStillThere?");

        assertNull(result);
    }
}