Add carrier regex filtering to VisualVoicemailSmsFilter

This CL adds a extra step in the VisualVoicemailSmsFilter which will
match the message body against the carrier specified regex. Even if
the message have a unknown format if it matches the regex it will
still be considered a VVM SMS.

For example, Verizon requires any SMS that starts with "//VZW" to be
dropped. Other cases are handled by the AppDirectedSms app, but
"//VZWVVM"  is left for the system visual voicemail. Previously
"//VZWVVM garbage data" will pass though the filter because it is not
a valid VVM SMS, which is against the specification of dropping
everything. After this CL it will match the regex and dropped.
The VVM client will also receive the message body, but ignore it.

Change-Id: I8d446d69d75914bf5fe63834cfebec92f7917711
Fixes: 30954955
diff --git a/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java b/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java
index ce991db..ca1d9e0 100644
--- a/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java
+++ b/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java
@@ -15,17 +15,21 @@
  */
 package com.android.internal.telephony;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.Intent;
 import android.provider.VoicemailContract;
 import android.telephony.SmsMessage;
 import android.telephony.TelephonyManager;
 import android.telephony.VisualVoicemailSmsFilterSettings;
+import android.util.ArrayMap;
 import android.util.Log;
-
 import com.android.internal.telephony.VisualVoicemailSmsParser.WrappedMessageData;
-
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
 
 public class VisualVoicemailSmsFilter {
 
@@ -33,6 +37,8 @@
 
     private static final String SYSTEM_VVM_CLIENT_PACKAGE = "com.android.phone";
 
+    private static Map<String, List<Pattern>> sPatterns;
+
     /**
      * 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 the visual
@@ -42,6 +48,11 @@
      *
      * <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} with {@link
+     * VoicemailContract#EXTRA_VOICEMAIL_SMS_MESSAGE_BODY} will be sent.
+     *
      * @return true if the SMS has been parsed to be a visual voicemail SMS and should be dropped
      */
     public static boolean filter(Context context, byte[][] pdus, String format, int destPort,
@@ -69,38 +80,83 @@
             WrappedMessageData messageData = VisualVoicemailSmsParser
                 .parseAlternativeFormat(asciiMessage);
             if (messageData != null) {
-                sendVvmSmsBroadcast(context, vvmClientPackage, subId, messageData);
+                sendVvmSmsBroadcast(context, vvmClientPackage, subId, messageData, null);
             }
             // 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);
-            if (messageData != null) {
-                sendVvmSmsBroadcast(context, vvmClientPackage, subId, messageData);
+        }
+        String clientPrefix = settings.clientPrefix;
+        WrappedMessageData messageData = VisualVoicemailSmsParser
+            .parse(clientPrefix, messageBody);
+        if (messageData != null) {
+            sendVvmSmsBroadcast(context, vvmClientPackage, subId, messageData, null);
+            return true;
+        }
+
+        buildPatternsMap(context);
+        String mccMnc = telephonyManager.getSimOperator(subId);
+
+        List<Pattern> patterns = sPatterns.get(mccMnc);
+        if (patterns == null || patterns.isEmpty()) {
+            return false;
+        }
+
+        for (Pattern pattern : patterns) {
+            if (pattern.matcher(messageBody).matches()) {
+                Log.w(TAG, "Incoming SMS matches pattern " + pattern + " but has illegal format, "
+                    + "still dropping as VVM SMS");
+                sendVvmSmsBroadcast(context, vvmClientPackage, subId, null, messageBody);
                 return true;
             }
         }
         return false;
     }
 
+    private static void buildPatternsMap(Context context) {
+        if (sPatterns != null) {
+            return;
+        }
+        sPatterns = new ArrayMap<>();
+        // TODO(twyen): build from CarrierConfig once public API can be updated.
+        for (String entry : context.getResources()
+            .getStringArray(com.android.internal.R.array.config_vvmSmsFilterRegexes)) {
+            String[] mccMncList = entry.split(";")[0].split(",");
+            Pattern pattern = Pattern.compile(entry.split(";")[1]);
+
+            for (String mccMnc : mccMncList) {
+                if (!sPatterns.containsKey(mccMnc)) {
+                    sPatterns.put(mccMnc, new ArrayList<>());
+                }
+                sPatterns.get(mccMnc).add(pattern);
+            }
+        }
+    }
+
     private static void sendVvmSmsBroadcast(Context context, String vvmClientPackage, int subId,
-        WrappedMessageData messageData) {
+        @Nullable WrappedMessageData messageData, @Nullable String messageBody) {
         Log.i(TAG, "VVM SMS received");
         Intent intent = new Intent(VoicemailContract.ACTION_VOICEMAIL_SMS_RECEIVED);
-        intent.putExtra(VoicemailContract.EXTRA_VOICEMAIL_SMS_PREFIX, messageData.prefix);
-        intent.putExtra(VoicemailContract.EXTRA_VOICEMAIL_SMS_FIELDS, messageData.fields);
+        if (messageData != null) {
+            intent.putExtra(VoicemailContract.EXTRA_VOICEMAIL_SMS_PREFIX, messageData.prefix);
+            intent.putExtra(VoicemailContract.EXTRA_VOICEMAIL_SMS_FIELDS, messageData.fields);
+        }
+        if (messageBody != null) {
+            intent.putExtra(VoicemailContract.EXTRA_VOICEMAIL_SMS_MESSAGE_BODY, messageBody);
+        }
         intent.putExtra(VoicemailContract.EXTRA_VOICEMAIL_SMS_SUBID, subId);
         intent.setPackage(vvmClientPackage);
         context.sendBroadcast(intent);
     }
 
+    /**
+     * @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) {
         StringBuilder builder = new StringBuilder();
         for (byte pdu[] : pdus) {
-            SmsMessage message =SmsMessage.createFromPdu(pdu, format);
+            SmsMessage message = SmsMessage.createFromPdu(pdu, format);
 
             if (message == null) {
                 // The PDU is not recognized by android