| /* |
| * 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;[email protected];pw=1" => |
| * "WrappedMessageData [fields={st=R, ipt=1, srv=1, dn=1, [email protected], 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;[email protected];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; |
| } |
| } |