Support VisualVoicemailSmsFilter

+ Implemented storage for related values in framework APIs.
  The storage is in device protected storage and can be read when the
  phone is locked.
+ OmtpMessageReceiver changed from using data sms to
  VOICEMAIL_SMS_RECEIVED sent by the filter.
+ Set filter values on activation and disable filter on deactivation.

Bug:27816386
Bug:27817303
Change-Id: I7542e6af54629b47fc321d96ea30c7f39b277e3f
(cherry picked from commit 7dfe5db8a43ddcccf1c386438d33903a7658a651)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9655756..781bd40 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -655,8 +655,7 @@
         <receiver android:name="com.android.phone.vvm.omtp.sms.OmtpMessageReceiver"
             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DATA_SMS_RECEIVED" />
-                <data android:scheme="sms" />
+                <action android:name="android.intent.action.VOICEMAIL_SMS_RECEIVED"/>
             </intent-filter>
         </receiver>
         <receiver
diff --git a/res/xml/vvm_config.xml b/res/xml/vvm_config.xml
index 5d120b8..83ff056 100644
--- a/res/xml/vvm_config.xml
+++ b/res/xml/vvm_config.xml
@@ -16,6 +16,18 @@
 
 <list name="carrier_config_list">
   <pbundle_as_map>
+    <!-- Test -->
+    <string-array name="mccmnc">
+      <item value="TEST"/>
+    </string-array>
+
+    <int name="vvm_port_number_int" value="20481"/>
+    <string name="vvm_destination_number_string">887</string>
+    <string name="vvm_type_string">vvm_type_omtp</string>
+    <boolean name="vvm_cellular_data_required_bool" value="true"/>
+  </pbundle_as_map>
+
+  <pbundle_as_map>
     <!-- Orange France -->
     <string-array name="mccmnc">
       <item value="20801"/>
@@ -23,11 +35,11 @@
     </string-array>
 
     <int name="vvm_port_number_int" value="20481"/>
-    <string name="vvm_destination_number_string" value="21101"/>
+    <string name="vvm_destination_number_string">21101</string>
     <string-array name="carrier_vvm_package_name_string_array">
       <item value="com.orange.vvm"/>
     </string-array>
-    <string name="vvm_type_string" value="vvm_type_omtp"/>
+    <string name="vvm_type_string">vvm_type_omtp</string>
     <boolean name="vvm_cellular_data_required_bool" value="true"/>
   </pbundle_as_map>
 
@@ -39,8 +51,8 @@
     </string-array>
 
     <int name="vvm_port_number_int" value="20481"/>
-    <string name="vvm_destination_number_string" value="881"/>
-    <string name="vvm_type_string" value="vvm_type_omtp"/>
+    <string name="vvm_destination_number_string">881</string>
+    <string name="vvm_type_string">vvm_type_omtp</string>
   </pbundle_as_map>
 
   <pbundle_as_map>
@@ -66,10 +78,14 @@
     </string-array>
 
     <int name="vvm_port_number_int" value="1808"/>
-    <string name="vvm_destination_number_string" value="122"/>
+    <int name="vvm_ssl_port_number_int" value="993"/>
+    <string name="vvm_destination_number_string">122</string>
     <string-array name="carrier_vvm_package_name_string_array">
       <item value="com.tmobile.vvm.application"/>
     </string-array>
-    <string name="vvm_type_string" value="vvm_type_cvvm"/>
+    <string name="vvm_type_string">vvm_type_cvvm</string>>
+    <string-array name="vvm_disabled_capabilities_string_array">
+      <item value="AUTH=DIGEST-MD5"/>
+    </string-array>
   </pbundle_as_map>
 </list>
\ No newline at end of file
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 0b3a80b..acffb79 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -16,6 +16,8 @@
 
 package com.android.phone;
 
+import static com.android.internal.telephony.PhoneConstants.SUBSCRIPTION_KEY;
+
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.content.ComponentName;
@@ -44,13 +46,13 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellInfo;
 import android.telephony.IccOpenLogicalChannelResponse;
+import android.telephony.ModemActivityInfo;
 import android.telephony.NeighboringCellInfo;
 import android.telephony.RadioAccessFamily;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
-import android.telephony.ModemActivityInfo;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
@@ -67,9 +69,9 @@
 import com.android.internal.telephony.MccTable;
 import com.android.internal.telephony.OperatorInfo;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.ProxyController;
-import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.uicc.IccIoResult;
@@ -79,8 +81,6 @@
 import com.android.internal.util.HexDump;
 import com.android.phone.settings.VoicemailNotificationSettingsUtil;
 
-import static com.android.internal.telephony.PhoneConstants.SUBSCRIPTION_KEY;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -1827,6 +1827,56 @@
         return success;
     }
 
+    @Override
+    public void setVisualVoicemailSmsFilterEnabled(int subId, boolean value) {
+        VisualVoicemailSmsFilterConfig
+                .setVisualVoicemailSmsFilterEnabled(mPhone.getContext(), subId, value);
+    }
+
+    @Override
+    public boolean isVisualVoicemailSmsFilterEnabled(String packageName, int subId) {
+        return VisualVoicemailSmsFilterConfig
+                .isVisualVoicemailSmsFilterEnabled(mPhone.getContext(), packageName, subId);
+    }
+
+    @Override
+    public void setVisualVoicemailSmsFilterClientPrefix(int subId, String prefix) {
+        VisualVoicemailSmsFilterConfig
+                .setVisualVoicemailSmsFilterClientPrefix(mPhone.getContext(), subId, prefix);
+    }
+
+    @Override
+    public String getVisualVoicemailSmsFilterClientPrefix(String packageName, int subId) {
+        return VisualVoicemailSmsFilterConfig
+                .getVisualVoicemailSmsFilterClientPrefix(mPhone.getContext(), packageName, subId);
+    }
+
+    @Override
+    public void setVisualVoicemailSmsFilterOriginatingNumbers(int subId, String[] numbers) {
+        VisualVoicemailSmsFilterConfig
+                .setVisualVoicemailSmsFilterOriginatingNumbers(mPhone.getContext(), subId, numbers);
+    }
+
+    @Override
+    public String[] getVisualVoicemailSmsFilterOriginatingNumbers(String packageName, int subId) {
+        return VisualVoicemailSmsFilterConfig
+                .getVisualVoicemailSmsFilterOriginatingNumbers(mPhone.getContext(), packageName,
+                        subId);
+    }
+
+    @Override
+    public void setVisualVoicemailSmsFilterDestinationPort(int subId, int port) {
+        VisualVoicemailSmsFilterConfig
+                .setVisualVoicemailSmsFilterDestinationPort(mPhone.getContext(), subId, port);
+    }
+
+    @Override
+    public int getVisualVoicemailSmsFilterDestinationPort(String packageName, int subId) {
+        return VisualVoicemailSmsFilterConfig
+                .getVisualVoicemailSmsFilterDestinationPort(mPhone.getContext(), packageName,
+                        subId);
+    }
+
     /**
      * Returns the unread count of voicemails
      */
diff --git a/src/com/android/phone/VisualVoicemailSmsFilterConfig.java b/src/com/android/phone/VisualVoicemailSmsFilterConfig.java
new file mode 100644
index 0000000..e0139ee
--- /dev/null
+++ b/src/com/android/phone/VisualVoicemailSmsFilterConfig.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.phone;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
+
+import java.util.Arrays;
+import java.util.Set;
+
+/**
+ * Stores the config values needed for visual voicemail sms filtering. The values from
+ * OmtpVvmCarrierConfigHelper are stored here during activation instead. These values are read and
+ * written through TelephonyManager.
+ */
+public class VisualVoicemailSmsFilterConfig {
+
+    private static final String VVM_SMS_FILTER_COFIG_SHARED_PREFS_KEY_PREFIX =
+            "vvm_sms_filter_config_";
+    private static final String ENABLED_KEY = "_enabled";
+    private static final String PREFIX_KEY = "_prefix";
+    private static final String ORIGINATING_NUMBERS_KEY = "_originating_numbers";
+    private static final String DESTINATION_PORT_KEY = "_destination_port";
+
+    public static void setVisualVoicemailSmsFilterEnabled(Context context, int subId,
+            boolean value) {
+        setBoolean(context, subId, ENABLED_KEY, value);
+    }
+
+    public static boolean isVisualVoicemailSmsFilterEnabled(Context context, String packageName,
+            int subId) {
+        return getBoolean(context, packageName, subId, ENABLED_KEY);
+    }
+
+    public static void setVisualVoicemailSmsFilterClientPrefix(Context context, int subId,
+            String prefix) {
+        setString(context, subId, PREFIX_KEY, prefix);
+    }
+
+    public static String getVisualVoicemailSmsFilterClientPrefix(Context context,
+            String packageName, int subId) {
+        return getString(context, packageName, subId, PREFIX_KEY);
+    }
+
+    public static void setVisualVoicemailSmsFilterOriginatingNumbers(Context context, int subId,
+            String[] numbers) {
+        ArraySet<String> set = new ArraySet<>();
+        set.addAll(Arrays.asList(numbers));
+        setStringSet(context, subId, ORIGINATING_NUMBERS_KEY, set);
+    }
+
+    public static String[] getVisualVoicemailSmsFilterOriginatingNumbers(Context context,
+            String packageName, int subId) {
+        Set<String> numbers = getStringSet(context, packageName, subId, ORIGINATING_NUMBERS_KEY);
+        return numbers.toArray(new String[numbers.size()]);
+    }
+
+    public static void setVisualVoicemailSmsFilterDestinationPort(Context context, int subId,
+            int port) {
+        setInt(context, subId, DESTINATION_PORT_KEY, port);
+    }
+
+    public static int getVisualVoicemailSmsFilterDestinationPort(Context context,
+            String packageName, int subId) {
+        return getInt(context, packageName, subId, DESTINATION_PORT_KEY,
+                TelephonyManager.VVM_SMS_FILTER_DESTINATION_PORT_ANY);
+    }
+
+    private static int getInt(Context context, String packageName, int subId, String key,
+            int defaultValue) {
+        SharedPreferences prefs = getSharedPreferences(context);
+        return prefs.getInt(makePerPhoneAccountKey(packageName, subId, key), defaultValue);
+    }
+
+    private static void setInt(Context context, int subId, String key, int value) {
+        SharedPreferences.Editor editor = getSharedPreferences(context).edit();
+        editor.putInt(makePerPhoneAccountKey(context.getOpPackageName(), subId, key), value);
+        editor.apply();
+    }
+
+    private static boolean getBoolean(Context context, String packageName, int subId, String key) {
+        SharedPreferences prefs = getSharedPreferences(context);
+        return prefs.getBoolean(makePerPhoneAccountKey(packageName, subId, key), false);
+    }
+
+    private static void setBoolean(Context context, int subId, String key, boolean value) {
+        SharedPreferences.Editor editor = getSharedPreferences(context).edit();
+        editor.putBoolean(makePerPhoneAccountKey(context.getOpPackageName(), subId, key), value);
+        editor.apply();
+    }
+
+    private static String getString(Context context, String packageName, int subId, String key) {
+        SharedPreferences prefs = getSharedPreferences(context);
+        return prefs.getString(makePerPhoneAccountKey(packageName, subId, key), null);
+    }
+
+    private static void setString(Context context, int subId, String key, String value) {
+        SharedPreferences.Editor editor = getSharedPreferences(context).edit();
+        editor.putString(makePerPhoneAccountKey(context.getOpPackageName(), subId, key), value);
+        editor.apply();
+    }
+
+    private static Set<String> getStringSet(Context context, String packageName, int subId,
+            String key) {
+        return getSharedPreferences(context)
+                .getStringSet(makePerPhoneAccountKey(packageName, subId, key), null);
+    }
+
+    private static void setStringSet(Context context, int subId, String key, Set<String> value) {
+        SharedPreferences.Editor editor = getSharedPreferences(context).edit();
+        editor.putStringSet(makePerPhoneAccountKey(context.getOpPackageName(), subId, key), value);
+        editor.apply();
+    }
+
+    private static SharedPreferences getSharedPreferences(Context context) {
+        return PreferenceManager
+                .getDefaultSharedPreferences(context.createDeviceProtectedStorageContext());
+    }
+
+    private static String makePerPhoneAccountKey(String packageName, int subId, String key) {
+        // TODO: make sure subId is persistent enough to serve as a key
+        return VVM_SMS_FILTER_COFIG_SHARED_PREFS_KEY_PREFIX + packageName + "_"
+                + subId + key;
+    }
+}
diff --git a/src/com/android/phone/vvm/omtp/OmtpConstants.java b/src/com/android/phone/vvm/omtp/OmtpConstants.java
index fa3cb63..5fc0317 100644
--- a/src/com/android/phone/vvm/omtp/OmtpConstants.java
+++ b/src/com/android/phone/vvm/omtp/OmtpConstants.java
@@ -28,9 +28,8 @@
     public static final String SMS_KEY_VALUE_SEPARATOR = "=";
     public static final String SMS_PREFIX_SEPARATOR = ":";
 
-    public static final String CLIENT_PREFIX = "//VVM";
-    public static final String SYNC_SMS_PREFIX = CLIENT_PREFIX + ":SYNC:";
-    public static final String STATUS_SMS_PREFIX = CLIENT_PREFIX + ":STATUS:";
+    public static final String SYNC_SMS_PREFIX = "SYNC";
+    public static final String STATUS_SMS_PREFIX = "STATUS";
 
     // This is the format designated by the OMTP spec.
     public static final String DATE_TIME_FORMAT = "dd/MM/yyyy HH:mm Z";
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
index 81db684..c8b37fd 100644
--- a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
@@ -69,6 +69,8 @@
             "vvm_ssl_port_number_int";
     static final String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY =
             "vvm_disabled_capabilities_string_array";
+    static final String KEY_VVM_CLIENT_PREFIX_STRING =
+            "vvm_client_prefix_string";
 
     private final Context mContext;
     private final int mSubId;
@@ -142,7 +144,11 @@
      * so by checking if the carrier's voicemail app is installed.
      */
     public boolean isEnabledByDefault() {
-        for (String packageName : getCarrierVvmPackageNames()) {
+        Set<String> carrierPackages = getCarrierVvmPackageNames();
+        if (carrierPackages == null) {
+            return true;
+        }
+        for (String packageName : carrierPackages) {
             try {
                 mContext.getPackageManager().getPackageInfo(packageName, 0);
                 return false;
@@ -221,7 +227,19 @@
         return result;
     }
 
+    public String getClientPrefix() {
+        String prefix = (String) getValue(KEY_VVM_CLIENT_PREFIX_STRING);
+        if (prefix != null) {
+            return prefix;
+        }
+        return "//VVM";
+    }
+
     public void startActivation() {
+        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+        telephonyManager.setVisualVoicemailSmsFilterEnabled(mSubId, true);
+        telephonyManager.setVisualVoicemailSmsFilterClientPrefix(mSubId, getClientPrefix());
+
         OmtpMessageSender messageSender = getMessageSender();
         if (messageSender != null) {
             Log.i(TAG, "Requesting VVM activation for subId: " + mSubId);
@@ -230,6 +248,8 @@
     }
 
     public void startDeactivation() {
+        mContext.getSystemService(TelephonyManager.class)
+                .setVisualVoicemailSmsFilterEnabled(mSubId, false);
         OmtpMessageSender messageSender = getMessageSender();
         if (messageSender != null) {
             Log.i(TAG, "Requesting VVM deactivation for subId: " + mSubId);
diff --git a/src/com/android/phone/vvm/omtp/TelephonyVvmConfigManager.java b/src/com/android/phone/vvm/omtp/TelephonyVvmConfigManager.java
index 3a1967f..e91481e 100644
--- a/src/com/android/phone/vvm/omtp/TelephonyVvmConfigManager.java
+++ b/src/com/android/phone/vvm/omtp/TelephonyVvmConfigManager.java
@@ -40,6 +40,8 @@
 
     private static final String TAG = "TelephonyVvmCfgMgr";
 
+    private static final boolean USE_DEBUG_CONFIG = false; //STOPSHIP if true
+
     private static final String TAG_PERSISTABLEMAP = "pbundle_as_map";
 
     static final String KEY_MCCMNC = "mccmnc";
@@ -62,6 +64,9 @@
 
     @Nullable
     public PersistableBundle getConfig(String mccMnc) {
+        if (USE_DEBUG_CONFIG) {
+            return mConfigs.get("TEST");
+        }
         return mConfigs.get(mccMnc);
     }
 
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
index 9ac37a4..67ffef7 100644
--- a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
@@ -20,15 +20,14 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.UserManager;
-import android.provider.Telephony;
 import android.provider.VoicemailContract;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.Voicemail;
-import android.telephony.SmsMessage;
+import android.telephony.SubscriptionManager;
 import android.util.Log;
 
-import com.android.internal.telephony.PhoneConstants;
 import com.android.phone.PhoneGlobals;
 import com.android.phone.PhoneUtils;
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
@@ -57,7 +56,8 @@
 
         mContext = context;
         mPhoneAccount = PhoneUtils.makePstnPhoneAccountHandle(
-                intent.getExtras().getInt(PhoneConstants.PHONE_KEY));
+                SubscriptionManager.getPhoneId(
+                        intent.getExtras().getInt(VoicemailContract.EXTRA_VOICEMAIL_SMS_SUBID)));
 
         if (mPhoneAccount == null) {
             Log.w(TAG, "Received message for null phone account");
@@ -69,41 +69,26 @@
             return;
         }
 
-        SmsMessage[] messages = Telephony.Sms.Intents.getMessagesFromIntent(intent);
+        String eventType = intent.getExtras()
+                .getString(VoicemailContract.EXTRA_VOICEMAIL_SMS_PREFIX);
+        Bundle data = intent.getExtras().getBundle(VoicemailContract.EXTRA_VOICEMAIL_SMS_FIELDS);
 
-        if (messages == null) {
-            Log.w(TAG, "Message does not exist in the intent.");
-            return;
+        if (eventType.equals(OmtpConstants.SYNC_SMS_PREFIX)) {
+            SyncMessage message = new SyncMessage(data);
+
+            Log.v(TAG, "Received SYNC sms for " + mPhoneAccount.getId() +
+                    " with event " + message.getSyncTriggerEvent());
+            LocalLogHelper.log(TAG, "Received SYNC sms for " + mPhoneAccount.getId() +
+                    " with event " + message.getSyncTriggerEvent());
+            processSync(message);
+        } else if (eventType.equals(OmtpConstants.STATUS_SMS_PREFIX)) {
+            Log.v(TAG, "Received STATUS sms for " + mPhoneAccount.getId());
+            LocalLogHelper.log(TAG, "Received Status sms for " + mPhoneAccount.getId());
+            StatusMessage message = new StatusMessage(data);
+            updateSource(message);
+        } else {
+            Log.e(TAG, "Unknown prefix: " + eventType);
         }
-
-        StringBuilder messageBody = new StringBuilder();
-
-        for (int i = 0; i < messages.length; i++) {
-            if (messages[i].mWrappedSmsMessage != null) {
-                messageBody.append(messages[i].getMessageBody());
-            }
-        }
-
-        WrappedMessageData messageData = OmtpSmsParser.parse(messageBody.toString());
-        if (messageData != null) {
-            if (messageData.getPrefix() == OmtpConstants.SYNC_SMS_PREFIX) {
-                SyncMessage message = new SyncMessage(messageData);
-
-                Log.v(TAG, "Received SYNC sms for " + mPhoneAccount.getId() +
-                        " with event " + message.getSyncTriggerEvent());
-                LocalLogHelper.log(TAG, "Received SYNC sms for " + mPhoneAccount.getId() +
-                        " with event " + message.getSyncTriggerEvent());
-                processSync(message);
-            } else if (messageData.getPrefix() == OmtpConstants.STATUS_SMS_PREFIX) {
-                Log.v(TAG, "Received STATUS sms for " + mPhoneAccount.getId());
-                LocalLogHelper.log(TAG, "Received Status sms for " + mPhoneAccount.getId());
-                StatusMessage message = new StatusMessage(messageData);
-                updateSource(message);
-            } else {
-                Log.e(TAG, "This should never have happened");
-            }
-        }
-        // Let this fall through: this is not a message we're interested in.
     }
 
     /**
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpSmsParser.java b/src/com/android/phone/vvm/omtp/sms/OmtpSmsParser.java
deleted file mode 100644
index 54a2a02..0000000
--- a/src/com/android/phone/vvm/omtp/sms/OmtpSmsParser.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package com.android.phone.vvm.omtp.sms;
-
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.phone.vvm.omtp.OmtpConstants;
-
-import java.util.Map;
-
-/**
- * OMTP SMS parser interface, for parsing SYNC and STATUS SMS sent by OMTP visual voicemail server.
- */
-public class OmtpSmsParser {
-    private static String TAG = "OmtpSmsParser";
-    /**
-     * Parses the supplied SMS body and returns back a structured OMTP message.
-     * Returns null if unable to parse the SMS body.
-     */
-    public static WrappedMessageData parse(String smsBody) {
-        if (smsBody == null) {
-            return null;
-        }
-
-        WrappedMessageData messageData = null;
-        if (smsBody.startsWith(OmtpConstants.SYNC_SMS_PREFIX)) {
-            messageData = new WrappedMessageData(OmtpConstants.SYNC_SMS_PREFIX,
-                    parseSmsBody(smsBody.substring(OmtpConstants.SYNC_SMS_PREFIX.length())));
-            // Check for a mandatory field.
-            String triggerEvent = messageData.extractString(OmtpConstants.SYNC_TRIGGER_EVENT);
-            if (triggerEvent == null) {
-                Log.e(TAG, "Missing mandatory field: " + OmtpConstants.SYNC_TRIGGER_EVENT);
-                return null;
-            }
-        } else if (smsBody.startsWith(OmtpConstants.STATUS_SMS_PREFIX)) {
-            messageData = new WrappedMessageData(OmtpConstants.STATUS_SMS_PREFIX,
-                    parseSmsBody(smsBody.substring(OmtpConstants.STATUS_SMS_PREFIX.length())));
-        }
-
-        return messageData;
-    }
-
-    /**
-     * Converts a String of key/value pairs into a Map object. The WrappedMessageData object
-     * contains helper functions to retrieve the values.
-     *
-     * e.g. "//VVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1"
-     * => "WrappedMessageData [mFields={st=R, ipt=1, srv=1, dn=1, u=eg@example.com, pw=1, rc=0}]"
-     *
-     * @param message The sms string with the prefix removed.
-     * @return A WrappedMessageData object containing the map.
-     */
-    private static Map<String, String> parseSmsBody(String message) {
-        Map<String, String> keyValues = new ArrayMap<String, String>();
-        String[] entries = message.split(OmtpConstants.SMS_FIELD_SEPARATOR);
-        for (String entry : entries) {
-            String[] keyValue = entry.split(OmtpConstants.SMS_KEY_VALUE_SEPARATOR);
-            if (keyValue.length != 2) {
-                continue;
-            }
-            keyValues.put(keyValue[0].trim(), keyValue[1].trim());
-        }
-
-        return keyValues;
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/sms/StatusMessage.java b/src/com/android/phone/vvm/omtp/sms/StatusMessage.java
index 7e4faac..4d8c815 100644
--- a/src/com/android/phone/vvm/omtp/sms/StatusMessage.java
+++ b/src/com/android/phone/vvm/omtp/sms/StatusMessage.java
@@ -15,6 +15,7 @@
  */
 package com.android.phone.vvm.omtp.sms;
 
+import android.os.Bundle;
 import android.telecom.Log;
 
 import com.android.phone.vvm.omtp.OmtpConstants;
@@ -59,20 +60,20 @@
                 + ", mSmtpPassword=" + Log.pii(mSmtpPassword) + "]";
     }
 
-    public StatusMessage(WrappedMessageData wrappedData) {
-        mProvisioningStatus = wrappedData.extractString(OmtpConstants.PROVISIONING_STATUS);
-        mStatusReturnCode = wrappedData.extractString(OmtpConstants.RETURN_CODE);
-        mSubscriptionUrl = wrappedData.extractString(OmtpConstants.SUBSCRIPTION_URL);
-        mServerAddress = wrappedData.extractString(OmtpConstants.SERVER_ADDRESS);
-        mTuiAccessNumber = wrappedData.extractString(OmtpConstants.TUI_ACCESS_NUMBER);
-        mClientSmsDestinationNumber = wrappedData.extractString(
+    public StatusMessage(Bundle wrappedData) {
+        mProvisioningStatus = wrappedData.getString(OmtpConstants.PROVISIONING_STATUS);
+        mStatusReturnCode = wrappedData.getString(OmtpConstants.RETURN_CODE);
+        mSubscriptionUrl = wrappedData.getString(OmtpConstants.SUBSCRIPTION_URL);
+        mServerAddress = wrappedData.getString(OmtpConstants.SERVER_ADDRESS);
+        mTuiAccessNumber = wrappedData.getString(OmtpConstants.TUI_ACCESS_NUMBER);
+        mClientSmsDestinationNumber = wrappedData.getString(
                 OmtpConstants.CLIENT_SMS_DESTINATION_NUMBER);
-        mImapPort = wrappedData.extractString(OmtpConstants.IMAP_PORT);
-        mImapUserName = wrappedData.extractString(OmtpConstants.IMAP_USER_NAME);
-        mImapPassword = wrappedData.extractString(OmtpConstants.IMAP_PASSWORD);
-        mSmtpPort = wrappedData.extractString(OmtpConstants.SMTP_PORT);
-        mSmtpUserName = wrappedData.extractString(OmtpConstants.SMTP_USER_NAME);
-        mSmtpPassword = wrappedData.extractString(OmtpConstants.SMTP_PASSWORD);
+        mImapPort = wrappedData.getString(OmtpConstants.IMAP_PORT);
+        mImapUserName = wrappedData.getString(OmtpConstants.IMAP_USER_NAME);
+        mImapPassword = wrappedData.getString(OmtpConstants.IMAP_PASSWORD);
+        mSmtpPort = wrappedData.getString(OmtpConstants.SMTP_PORT);
+        mSmtpUserName = wrappedData.getString(OmtpConstants.SMTP_USER_NAME);
+        mSmtpPassword = wrappedData.getString(OmtpConstants.SMTP_PASSWORD);
     }
 
     /**
diff --git a/src/com/android/phone/vvm/omtp/sms/SyncMessage.java b/src/com/android/phone/vvm/omtp/sms/SyncMessage.java
index 6829981..1e565da 100644
--- a/src/com/android/phone/vvm/omtp/sms/SyncMessage.java
+++ b/src/com/android/phone/vvm/omtp/sms/SyncMessage.java
@@ -15,8 +15,14 @@
  */
 package com.android.phone.vvm.omtp.sms;
 
+import android.os.Bundle;
+
 import com.android.phone.vvm.omtp.OmtpConstants;
 
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+
 /**
  * Structured data representation of an OMTP SYNC message.
  *
@@ -49,16 +55,25 @@
                 + ", mMsgTimeMillis=" + mMsgTimeMillis + "]";
     }
 
-    public SyncMessage(WrappedMessageData wrappedData) {
-        mSyncTriggerEvent = wrappedData.extractString(OmtpConstants.SYNC_TRIGGER_EVENT);
-        mMessageId = wrappedData.extractString(OmtpConstants.MESSAGE_UID);
-        mMessageLength = wrappedData.extractInteger(OmtpConstants.MESSAGE_LENGTH);
-        mContentType = wrappedData.extractString(OmtpConstants.CONTENT_TYPE);
-        mSender = wrappedData.extractString(OmtpConstants.SENDER);
-        mNewMessageCount = wrappedData.extractInteger(OmtpConstants.NUM_MESSAGE_COUNT);
-        mMsgTimeMillis = wrappedData.extractTime(OmtpConstants.TIME);
+    public SyncMessage(Bundle wrappedData) {
+        mSyncTriggerEvent = wrappedData.getString(OmtpConstants.SYNC_TRIGGER_EVENT);
+        mMessageId = wrappedData.getString(OmtpConstants.MESSAGE_UID);
+        mMessageLength = Integer.parseInt(wrappedData.getString(OmtpConstants.MESSAGE_LENGTH));
+        mContentType = wrappedData.getString(OmtpConstants.CONTENT_TYPE);
+        mSender = wrappedData.getString(OmtpConstants.SENDER);
+        mNewMessageCount = Integer.parseInt(wrappedData.getString(OmtpConstants.NUM_MESSAGE_COUNT));
+        mMsgTimeMillis = parseTime(wrappedData.getString(OmtpConstants.TIME));
     }
 
+    static Long parseTime(String value) {
+        try {
+            return new SimpleDateFormat(
+                    OmtpConstants.DATE_TIME_FORMAT, Locale.US)
+                    .parse(value).getTime();
+        } catch (ParseException e) {
+            return 0L;
+        }
+    }
     /**
      * @return the event that triggered the sync message. This is a mandatory field and must always
      * be set.
diff --git a/src/com/android/phone/vvm/omtp/sms/WrappedMessageData.java b/src/com/android/phone/vvm/omtp/sms/WrappedMessageData.java
deleted file mode 100644
index b4c86d4..0000000
--- a/src/com/android/phone/vvm/omtp/sms/WrappedMessageData.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package com.android.phone.vvm.omtp.sms;
-
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.phone.vvm.omtp.OmtpConstants;
-
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Locale;
-import java.util.Map;
-
-/**
- * Class wrapping the raw OMTP message data, internally represented as as map of all key-value pairs
- * found in the SMS body.
- * <p>
- * Provides convenience methods to extract parse fields of different types.
- * <p>
- * All the methods return null if either the field was not present or it could not be parsed.
- */
-public class WrappedMessageData {
-    private final String TAG = "WrappedMessageData";
-    private final String mPrefix;
-    private final Map<String, String> mFields;
-
-    @Override
-    public String toString() {
-        return "WrappedMessageData [mFields=" + mFields + "]";
-    }
-
-    WrappedMessageData(String prefix, Map<String, String> keyValues) {
-        mPrefix = prefix;
-        mFields = new ArrayMap<String, String>();
-        mFields.putAll(keyValues);
-    }
-
-    /**
-     * @return The String prefix of the message, designating whether this is the message data of a
-     * STATUS or SYNC sms.
-     */
-    String getPrefix() {
-        return mPrefix;
-    }
-
-    /**
-     * Extracts the requested field from underlying data and returns the String value as is.
-     *
-     * @param field The requested field.
-     * @return the parsed string value, or null if the field was not present or not valid.
-     */
-    String extractString(final String field) {
-        String value = mFields.get(field);
-        if (value == null) {
-            return null;
-        }
-
-        String[] possibleValues = OmtpConstants.possibleValuesMap.get(field);
-        if (possibleValues == null) {
-            return value;
-        }
-        for (int i = 0; i < possibleValues.length; i++) {
-            if (TextUtils.equals(value, possibleValues[i])) {
-                return value;
-            }
-        }
-        Log.e(TAG, "extractString - value \"" + value +
-                "\" of field \"" + field + "\" is not allowed.");
-        return null;
-    }
-
-    /**
-     * Extracts the requested field from underlying data and parses it as an {@link Integer}.
-     *
-     * @param field The requested field.
-     * @return the parsed integer value, or null if the field was not present.
-     */
-    Integer extractInteger(final String field) {
-        String value = mFields.get(field);
-        if (value == null) {
-            return null;
-        }
-
-        try {
-            return Integer.decode(value);
-        } catch (NumberFormatException e) {
-            Log.e(TAG, "extractInteger - could not parse integer: " + value);
-            return null;
-        }
-    }
-
-    /**
-     * Extracts the requested field from underlying data and parses it as a date/time represented in
-     * {@link OmtpConstants#DATE_TIME_FORMAT} format.
-     *
-     * @param field The requested field.
-     * @return the parsed string value, or null if the field was not present.
-     */
-    Long extractTime(final String field) {
-        String value = mFields.get(field);
-        if (value == null) {
-            return null;
-        }
-
-        try {
-            return new SimpleDateFormat(
-                    OmtpConstants.DATE_TIME_FORMAT, Locale.US).parse(value).getTime();
-        } catch (ParseException e) {
-            Log.e(TAG, "extractTime - could not parse time: " + value);
-            return null;
-        }
-    }
-}
\ No newline at end of file