/*
* Copyright (C) 2018 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.res.XmlResourceParser;
import android.support.annotation.VisibleForTesting;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* CarrierXmlParser is a xml parser. It parses the carrier's ussd format from carrier_ss_string.xml.
* The carrier_ss_string.xml defines carrier's ussd structure and meaning in res/xml folder.
* Some carrier has specific ussd structure ,developer can add new xml and xml is named
* carrier_ss_string_carrierId.xml. The carrierId is a number and is defined in
* here
* For example: carrier_ss_string_850.xml
*
* How do use CarrierXmlParser?
* For example:
* @see CallForwardEditPreference
* TelephonyManager telephonyManager = new TelephonyManager(getContext(),phone.getSubId());
* CarrierXmlParser = new CarrierXmlParser(getContext(), telephonyManager.getSimCarrierId());
*
* //make a ussd command
* String newUssdCommand = mCarrierXmlParser.getFeature(
* CarrierXmlParser.FEATURE_CALL_FORWARDING).makeCommand(inputAction, inputCfInfo);
* //analyze ussd result
* HashMap analysisResult = mCarrierXmlParser.getFeature(
* CarrierXmlParser.FEATURE_CALL_FORWARDING)
* .getResponseSet(mSsAction, response.toString());
*/
public class CarrierXmlParser {
public static final String LOG_TAG = "CarrierXmlParser";
private static final boolean DEBUG = true;
private static final String STAR_SIGN = "*";
private static final String POUND_SIGN = "#";
private static final String TAG_SIGN = "tag_";
// To define feature's item name in xml
public static final String FEATURE_CALL_FORWARDING = "callforwarding";
public static final String FEATURE_CALLER_ID = "callerid";
// COMMAND_NAME is xml's command name.
public static final String TAG_COMMAND_NAME_QUERY = "query";
public static final String TAG_COMMAND_NAME_ACTIVATE = "activate";
public static final String TAG_COMMAND_NAME_DEACTIVATE = "deactivate";
// To define string level in xml.
// level 1
private static final String TAG_FEATURE = "feature";
private static final String TAG_REGULAR_PARSER = "regular_parser";
// level 2
private static final String TAG_COMMAND = "command";
// level 3
private static final String TAG_SERVICE_CODE = "service_code";
private static final String TAG_ACTION_CODE = "action_code";
private static final String TAG_PARAMETER = "parameter";
private static final String TAG_RESPONSE_FORMAT = "response_format";
private static final String TAG_COMMAND_RESULT = "command_result";
// level 4
private static final String TAG_ENTRY = "entry";
private static final String ATTR_NAME = "name";
private static final String ATTR_PARAMETER_NUM = "number";
private static final String ATTR_POSITION = "position";
private static final String ATTR_RESULT_KEY = "key";
private static final String ATTR_DEFINITION_KEY = "definition";
HashMap mFeatureMaps;
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
static String sParserFormat = "";
// TAG_ENTRY_NUMBER and TAG_ENTRY_TIME is xml's entry value.
// This is mapping user's input value. For example: number,time ...
// When UI makes command ,it will map the value and insert this value at position location.
// How to use it?
// The UI calls CarrierXmlParser's makeCommand function and inputs the hashmap which
// includes tag_name and user's input value.
// For example: User calls CarrierXmlParser's makeCommand in call forwarding , and inputs
// the hashmap {,}
// If developer wants to add new one, xml string should the same as hashmap's name.
public static final String TAG_ENTRY_NUMBER = "tag_number";
public static final String TAG_ENTRY_TIME = "tag_time";
// "response_format" key
// The key of "response_format" should define as below in xml.
// The UI will use it to define value from the response command
// and use the data show the screen.
public static final String TAG_RESPONSE_STATUS = "status_code";
public static final String TAG_RESPONSE_STATUS_ERROR = "RESPONSE_ERROR";
public static final String TAG_RESPONSE_NUMBER = "number";
public static final String TAG_RESPONSE_TIME = "time";
// This is the definition for the entry's key in response_format.
// Xml's COMMAND_RESULT_DEFINITION should same as below.
public static final String TAG_COMMAND_RESULT_DEFINITION_ACTIVATE = "activate";
public static final String TAG_COMMAND_RESULT_DEFINITION_DEACTIVATE = "deactivate";
public static final String TAG_COMMAND_RESULT_DEFINITION_UNREGISTER = "unregister";
public static final String TAG_COMMAND_RESULT_DEFINITION_OK = "ok";
public static final String TAG_COMMAND_RESULT_DEFINITION_FAIL = "fail";
/**
* UssdParser is a string parser. It parses the USSD response message.
*/
public static class UssdParser {
private Vector mParserStr = new Vector();
private Pattern mPatternSuppServiceResponse;
public UssdParser(String inputParserFormat) {
mPatternSuppServiceResponse = Pattern.compile(inputParserFormat);
}
/**
* This function is a parser and analyzes the USSD responses message.
*
* @param responseString The USSD responses message.
*/
public void newFromResponseString(String responseString) {
Matcher m;
m = mPatternSuppServiceResponse.matcher(responseString);
if (m.matches()) {
mParserStr.clear();
int groupSize = m.groupCount();
for (int i = 0; i <= groupSize; i++) {
if (!TextUtils.isEmpty(m.group(i))) {
mParserStr.add(m.group(i));
} else {
mParserStr.add("");
}
}
} else {
Log.d(LOG_TAG, "no match");
}
}
/**
* To get the UssdParser result.
*/
public Vector getResult() {
return mParserStr;
}
}
/**
* CarrierXmlParser parses command from xml and saves in SsEntry class.
*/
public static class SsEntry {
public enum SSAction {
UNKNOWN,
QUERY,
UPDATE_ACTIVATE,
UPDATE_DEACTIVATE
}
public String serviceCode;
public SSAction ssAction = SSAction.UNKNOWN;
public String actionCode;
public HashMap commandParameter = new HashMap();
public HashMap responseFormat = new HashMap();
public SsEntry(String action) {
if (action.equals(TAG_COMMAND_NAME_QUERY)) {
ssAction = SSAction.QUERY;
} else if (action.equals(TAG_COMMAND_NAME_ACTIVATE)) {
ssAction = SSAction.UPDATE_ACTIVATE;
} else if (action.equals(TAG_COMMAND_NAME_DEACTIVATE)) {
ssAction = SSAction.UPDATE_DEACTIVATE;
}
}
@Override
public String toString() {
return "SsEntry serviceCode:" + serviceCode
+ ", ssAction:" + ssAction
+ ", actionCode:" + actionCode
+ ", commandParameter:" + commandParameter.toString()
+ ", responseFormat:" + responseFormat.toString();
}
/**
* To get the caller id command by xml's structure.
*/
public String getCommandStructure() {
String result = actionCode + serviceCode;
int mapSize = commandParameter.size();
int parameterIndex = 0;
while (parameterIndex < mapSize) {
parameterIndex++;
if (commandParameter.containsKey(parameterIndex)) {
if (commandParameter.get(parameterIndex) != null) {
result = result + STAR_SIGN + commandParameter.get(parameterIndex);
}
}
}
result = result + POUND_SIGN;
Log.d(LOG_TAG, "getCommandStructure result:" + result);
return result;
}
/**
* To make ussd command by xml's structure.
*
* @param inputInformationSet This is a map which includes parameters from UI.
* The name of map is mapping parameter's key of entry in xml.
*/
public String makeCommand(Map inputInformationSet) {
String result = actionCode + serviceCode;
int mapSize = commandParameter.size();
int parameterIndex = 0;
int counter = 1;
Map informationSet = inputInformationSet;
while (parameterIndex < mapSize) {
if (commandParameter.containsKey(counter)) {
String getInputValue = "";
// need to handle tag_XXXX
if (informationSet != null && informationSet.size() > 0
&& informationSet.containsKey(commandParameter.get(counter))) {
getInputValue = informationSet.get(commandParameter.get(counter));
}
if (TextUtils.isEmpty(getInputValue)) {
result = result + STAR_SIGN + commandParameter.get(counter);
} else {
result = result + STAR_SIGN + informationSet.get(
commandParameter.get(counter));
}
parameterIndex++;
} else {
result = result + STAR_SIGN;
}
counter++;
}
result = result + POUND_SIGN;
return result;
}
/**
* To parse the specific key and value from response message.
*
* @param inputResponse This is a ussd response message from network.
* @param responseDefine This is the definition for "command_result" in xml.
*/
public HashMap getResponseSet(String inputResponse,
HashMap> responseDefine) {
HashMap responseSet = new HashMap();
if (TextUtils.isEmpty(sParserFormat)) {
return responseSet;
}
UssdParser parserResult = new UssdParser(sParserFormat);
parserResult.newFromResponseString(inputResponse);
if (parserResult == null) {
return responseSet;
}
Vector result = parserResult.getResult();
if (result == null) {
return responseSet;
}
for (int i = 0; i < result.size(); i++) {
if (responseFormat.containsKey(i)) {
String defineString = "";
if (responseDefine.containsKey(responseFormat.get(i))) {
for (int x = 0; x < responseDefine.get(responseFormat.get(i)).size(); x++) {
defineString = ((SsResultEntry) responseDefine.get(
responseFormat.get(i)).get(x)).getDefinitionByCompareValue(
result.get(i));
if (!TextUtils.isEmpty(defineString)) {
break;
}
}
// if status_code do not match definition value, we will set command error.
if (TAG_RESPONSE_STATUS.equals(responseFormat.get(i))) {
if (TextUtils.isEmpty(defineString)) {
responseSet.put(TAG_RESPONSE_STATUS_ERROR,
TAG_RESPONSE_STATUS_ERROR);
}
}
}
if (TextUtils.isEmpty(defineString)) {
responseSet.put(responseFormat.get(i), result.get(i));
} else {
responseSet.put(responseFormat.get(i), defineString);
}
}
}
return responseSet;
}
}
/**
* CarrierXmlParser parses command_result from xml and saves in SsResultEntry class.
*/
public static class SsResultEntry {
String mDefinition;
String mCompareValue;
public SsResultEntry() {
}
@Override
public String toString() {
return "SsResultEntry mDefinition:" + mDefinition
+ ", mCompareValue:" + mCompareValue;
}
/**
* If mCompareValue item is the same as compare value,it will return the mDefinition.
*
* @param inputValue This is the entry of response command's value.
* @return mDefinition or null.
*/
public String getDefinitionByCompareValue(String inputValue) {
if (mCompareValue.equals(inputValue)) {
return mDefinition;
}
return null;
}
}
/**
* CarrierXmlParser parses feature from xml and saves in SsFeature class.
*/
public class SsFeature {
public HashMap ssEntryHashMap =
new HashMap();
public HashMap> responseCode =
new HashMap>();
public SsFeature() {
}
private String getResponseCodeString() {
String result = "";
for (Map.Entry> entry : responseCode.entrySet()) {
ArrayList values = entry.getValue();
for (int i = 0; i < values.size(); i++) {
result += "value of i is " + ((SsResultEntry) values.get(i)).toString();
}
}
return result;
}
@Override
public String toString() {
return getResponseCodeString();
}
/**
* To get the caller id command by xml's structure.
*
* @param inputAction This is action_code of command item from xml.
*/
public String getCommandStructure(SsEntry.SSAction inputAction) {
SsEntry entry = ssEntryHashMap.get(inputAction);
return entry.getCommandStructure();
}
/**
* To make the ussd command by xml structure
*
* @param inputAction This is action_code of command item from xml.
* @param inputInformationSet This is for parameter of command.
* @return The ussd command string.
*/
public String makeCommand(SsEntry.SSAction inputAction,
Map inputInformationSet) {
SsEntry entry = ssEntryHashMap.get(inputAction);
return entry.makeCommand(inputInformationSet);
}
/**
* To parse the special key and value from response message.
*
* @param inputAction This is action_code of command item from xml.
* @param inputResponse This is response message from network.
* @return The set includes specific key and value.
*/
public HashMap getResponseSet(SsEntry.SSAction inputAction,
String inputResponse) {
SsEntry entry = ssEntryHashMap.get(inputAction);
return entry.getResponseSet(inputResponse, responseCode);
}
}
/**
* @param context context to get res's xml
* @param carrierId carrier id of the current subscription. The carrier ID is an Android
* platform-wide identifier for a carrier. AOSP maintains carrier ID assignments in
* here
*/
public CarrierXmlParser(Context context, int carrierId) {
try {
int xmlResId = 0;
if (carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
String xmlResIdName = "carrier_ss_string" + "_" + carrierId;
xmlResId = context.getResources().getIdentifier(xmlResIdName, "xml",
context.getPackageName());
}
if (xmlResId == 0) {
xmlResId = R.xml.carrier_ss_string;
}
Log.d(LOG_TAG, "carrierId: " + carrierId);
XmlResourceParser parser = context.getResources().getXml(xmlResId);
mFeatureMaps = parseXml(parser);
} catch (Exception e) {
Log.d(LOG_TAG, "Error parsing XML " + e.toString());
}
}
private HashMap parseXml(XmlResourceParser parser) throws IOException {
HashMap features = new HashMap();
try {
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
if (TAG_REGULAR_PARSER.equals(parser.getName())) {
sParserFormat = readText(parser);
Log.d(LOG_TAG, "sParserFormat " + sParserFormat);
} else if (TAG_FEATURE.equals(parser.getName())) {
String featureName = getSpecificAttributeValue(parser, ATTR_NAME);
if (!TextUtils.isEmpty(featureName)) {
SsFeature feature = generateFeatureList(parser);
features.put(featureName, feature);
Log.d(LOG_TAG, "add " + featureName + " to map:" + feature.toString());
}
}
}
parser.next();
eventType = parser.getEventType();
}
} catch (XmlPullParserException e) {
e.printStackTrace();
}
return features;
}
private SsFeature generateFeatureList(XmlResourceParser parser)
throws XmlPullParserException, IOException {
SsFeature ssfeature = new SsFeature();
int outerDepth = parser.getDepth();
Log.d(LOG_TAG, "generateFeatureList outerDepth" + outerDepth);
while (parser.next() != XmlPullParser.END_DOCUMENT) {
Log.d(LOG_TAG, "generateFeatureList parser.getDepth()" + parser.getDepth());
int eventType = parser.getEventType();
if (eventType == XmlPullParser.END_TAG
&& outerDepth == parser.getDepth()) {
break;
}
if (eventType != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
// Starts by looking for the command tag.
if (TAG_COMMAND.equals(name)) {
SsEntry entry = readCommandEntry(parser);
ssfeature.ssEntryHashMap.put(entry.ssAction, entry);
} else if (TAG_COMMAND_RESULT.equals(name)) {
readCommandResultEntry(parser, ssfeature);
}
}
return ssfeature;
}
private void readCommandResultEntry(XmlResourceParser parser, SsFeature ssFeature)
throws XmlPullParserException, IOException {
while (parser.next() != XmlPullParser.END_DOCUMENT) {
int eventType = parser.getEventType();
if (eventType == XmlPullParser.END_TAG
&& TAG_COMMAND_RESULT.equals(parser.getName())) {
break;
}
if (eventType == XmlPullParser.START_TAG
&& TAG_ENTRY.equals(parser.getName())) {
String key = getSpecificAttributeValue(parser, ATTR_RESULT_KEY);
if (!TextUtils.isEmpty(key)) {
SsResultEntry entry = new SsResultEntry();
entry.mDefinition = getSpecificAttributeValue(parser, ATTR_DEFINITION_KEY);
entry.mCompareValue = readText(parser);
if (ssFeature.responseCode.containsKey(key)) {
ssFeature.responseCode.get(key).add(entry);
} else {
ArrayList arrayList = new ArrayList<>();
arrayList.add(entry);
ssFeature.responseCode.put(key, arrayList);
}
}
}
}
}
private SsEntry readCommandEntry(XmlResourceParser parser)
throws XmlPullParserException, IOException {
int outerDepth = parser.getDepth();
String command_action = getSpecificAttributeValue(parser, ATTR_NAME);
SsEntry entry = new SsEntry(command_action);
while (parser.next() != XmlPullParser.END_DOCUMENT) {
int eventType = parser.getEventType();
if (eventType == XmlPullParser.END_TAG
&& outerDepth == parser.getDepth()) {
break;
}
if (eventType != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
if (TAG_SERVICE_CODE.equals(name)) {
entry.serviceCode = readText(parser);
} else if (TAG_ACTION_CODE.equals(name)) {
entry.actionCode = readText(parser);
} else if (TAG_PARAMETER.equals(name)) {
String number = getSpecificAttributeValue(parser, ATTR_PARAMETER_NUM);
if (!TextUtils.isEmpty(number)) {
readParameters(parser, entry, Integer.valueOf(number), TAG_PARAMETER);
}
} else if (TAG_RESPONSE_FORMAT.equals(name)) {
String number = getSpecificAttributeValue(parser, ATTR_PARAMETER_NUM);
if (!TextUtils.isEmpty(number)) {
readParameters(parser, entry, Integer.valueOf(number), TAG_RESPONSE_FORMAT);
}
}
}
Log.d(LOG_TAG, "ssEntry:" + entry.toString());
return entry;
}
private void readParameters(XmlResourceParser parser, SsEntry entry, int num, String parentTag)
throws IOException, XmlPullParserException {
Log.d(LOG_TAG, "readParameters() nume:" + num);
int i = 0;
while (i < num) {
if (parser.next() == XmlPullParser.START_TAG) {
String name = parser.getName();
if (TAG_ENTRY.equals(name)) {
String position = getSpecificAttributeValue(parser, ATTR_POSITION);
if (!TextUtils.isEmpty(position)) {
if (TAG_PARAMETER.equals(parentTag)) {
String value = readText(parser);
if (!TextUtils.isEmpty(value)) {
entry.commandParameter.put(Integer.valueOf(position), value);
}
} else if (TAG_RESPONSE_FORMAT.equals(parentTag)) {
String key = getSpecificAttributeValue(parser, ATTR_RESULT_KEY);
if (!TextUtils.isEmpty(key)) {
entry.responseFormat.put(Integer.valueOf(position), key);
}
}
i++;
}
}
}
}
}
private String getSpecificAttributeValue(XmlResourceParser parser, String attrTag) {
String value = "";
for (int i = 0; i < parser.getAttributeCount(); i++) {
if (attrTag.equals(parser.getAttributeName(i))) {
value = parser.getAttributeValue(i);
}
}
return value;
}
private String readText(XmlResourceParser parser) throws IOException, XmlPullParserException {
String result = "";
if (parser.next() == XmlPullParser.TEXT) {
result = parser.getText();
parser.nextTag();
}
return result;
}
/**
* CarrierXmlParser parses the xml and saves in mFeatureMap.
* To use this function get feature from the mFeatureMaps.
*
* @param inputFeatureName This is feature's name from xml.
*/
public SsFeature getFeature(String inputFeatureName) {
return mFeatureMaps.get(inputFeatureName);
}
/**
* To check the command which is dialed by user is caller id command.
*
* If it is a caller id command which sets to activate, return the {@code
* SsEntry.SSAction.UPDATE_ACTIVATE}.
* If it is a caller id command which sets to deactivate, return the {@code
* SsEntry.SSAction.UPDATE_DEACTIVATE}.
* If it is not a caller id command, return the {@code SsEntry.SSAction.UNKNOWN}.
*
* @param inputCommand This is caller id's ussd command which is dialed by user.
* @return {@link SsEntry.SSAction}
*/
public SsEntry.SSAction getCallerIdUssdCommandAction(String inputCommand) {
if (isCallerIdActivate(inputCommand)) {
return SsEntry.SSAction.UPDATE_ACTIVATE;
}
if (isCallerIdDeactivate(inputCommand)) {
return SsEntry.SSAction.UPDATE_DEACTIVATE;
}
return SsEntry.SSAction.UNKNOWN;
}
private String getCallerIdActivateCommandFromXml() {
return getFeature(FEATURE_CALLER_ID).getCommandStructure(SsEntry.SSAction.UPDATE_ACTIVATE);
}
private String getCallerIdDeactivateCommandFromXml() {
return getFeature(FEATURE_CALLER_ID).getCommandStructure(
SsEntry.SSAction.UPDATE_DEACTIVATE);
}
private boolean isCallerIdActivate(String inputStr) {
String activateStr = getCallerIdActivateCommandFromXml();
return compareCommand(activateStr, inputStr);
}
private boolean isCallerIdDeactivate(String inputStr) {
String activateStr = getCallerIdDeactivateCommandFromXml();
return compareCommand(activateStr, inputStr);
}
private boolean compareCommand(String activateStr, String inputStr) {
String[] activateArray = activateStr.split("\\" + STAR_SIGN);
String[] inputArray = inputStr.split("\\" + STAR_SIGN);
if (activateArray.length == 0 || inputArray.length == 0) {
return false;
}
for (int i = 0; i < activateArray.length; i++) {
if (activateArray[i].startsWith(TAG_SIGN)) {
continue;
}
if (!activateArray[i].equals(inputArray[i])) {
Log.d(LOG_TAG, "compare fails:" + activateStr + "," + inputStr);
return false;
}
}
Log.d(LOG_TAG, "compare success");
return true;
}
}