154 lines
5.6 KiB
Java
154 lines
5.6 KiB
Java
/*
|
|
* 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. <p> 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 <key>=<value>.
|
|
// As the OMTP spec both key and value are required, but in some cases carriers will
|
|
// send an SMS with missing value, so only the presence of the key is enforced.
|
|
// For example, an SMS for a voicemail from restricted number might have "s=" for the
|
|
// sender field, instead of omitting the field.
|
|
int separatorIndex = entry.indexOf("=");
|
|
if (separatorIndex == -1 || separatorIndex == 0) {
|
|
// No separator or no key.
|
|
// For example "foo" or "=value".
|
|
// A VVM SMS should have all of its' field valid.
|
|
return null;
|
|
}
|
|
String key = entry.substring(0, separatorIndex);
|
|
String value = entry.substring(separatorIndex + 1);
|
|
keyValues.putString(key, value);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|