/* * 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.internal.telephony; import android.annotation.Nullable; 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.
All the methods return null if either the field was not
* present or it could not be parsed.
*/
public static class WrappedMessageData {
public final String prefix;
public final Bundle fields;
@Override
public String toString() {
return "WrappedMessageData [type=" + prefix + " fields=" + fields + "]";
}
WrappedMessageData(String prefix, Bundle keyValues) {
this.prefix = prefix;
fields = keyValues;
}
}
/**
* Parses the supplied SMS body and returns back a structured OMTP message. Returns null if
* unable to parse the SMS body.
*/
@Nullable
public static WrappedMessageData parse(String clientPrefix, String smsBody) {
try {
if (!smsBody.startsWith(clientPrefix)) {
return null;
}
int prefixEnd = clientPrefix.length();
if (!(smsBody.charAt(prefixEnd) == ':')) {
return null;
}
int eventTypeEnd = smsBody.indexOf(":", prefixEnd + 1);
if (eventTypeEnd == -1) {
return null;
}
String eventType = smsBody.substring(prefixEnd + 1, eventTypeEnd);
Bundle fields = parseSmsBody(smsBody.substring(eventTypeEnd + 1));
if (fields == null) {
return null;
}
return new WrappedMessageData(eventType, fields);
} catch (IndexOutOfBoundsException e) {
return null;
}
}
/**
* 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 [fields={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.
*/
@Nullable
private static Bundle parseSmsBody(String message) {
// TODO: ensure fail if format does not match
Bundle keyValues = new Bundle();
String[] entries = message.split(";");
for (String entry : entries) {
if (entry.length() == 0) {
continue;
}
// The format for a field is "MBOXUPDATE?m=1;server=example.com;port=143;name=foo@example.com;pw=foo".
*
* 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;
}
}