blob: 1ea31503bb7b99e12197d1895222429497456b7f [file] [log] [blame]
/*
* Copyright (C) 2007 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 android.bluetooth;
import android.bluetooth.AtCommandHandler;
import android.bluetooth.AtCommandResult;
import java.util.*;
/**
* An AT (Hayes command) Parser based on (a subset of) the ITU-T V.250 standard.
* <p>
*
* Conforment with the subset of V.250 required for implementation of the
* Bluetooth Headset and Handsfree Profiles, as per Bluetooth SIP
* specifications. Also implements some V.250 features not required by
* Bluetooth - such as chained commands.<p>
*
* Command handlers are registered with an AtParser object. These handlers are
* invoked when command lines are processed by AtParser's process() method.<p>
*
* The AtParser object accepts a new command line to parse via its process()
* method. It breaks each command line into one or more commands. Each command
* is parsed for name, type, and (optional) arguments, and an appropriate
* external handler method is called through the AtCommandHandler interface.
*
* The command types are<ul>
* <li>Basic Command. For example "ATDT1234567890". Basic command names are a
* single character (e.g. "D"), and everything following this character is
* passed to the handler as a string argument (e.g. "T1234567890").
* <li>Action Command. For example "AT+CIMI". The command name is "CIMI", and
* there are no arguments for action commands.
* <li>Read Command. For example "AT+VGM?". The command name is "VGM", and there
* are no arguments for get commands.
* <li>Set Command. For example "AT+VGM=14". The command name is "VGM", and
* there is a single integer argument in this case. In the general case then
* can be zero or more arguments (comma deliminated) each of integer or string
* form.
* <li>Test Command. For example "AT+VGM=?. No arguments.
* </ul>
*
* In V.250 the last four command types are known as Extended Commands, and
* they are used heavily in Bluetooth.<p>
*
* Basic commands cannot be chained in this implementation. For Bluetooth
* headset/handsfree use this is acceptable, because they only use the basic
* commands ATA and ATD, which are not allowed to be chained. For general V.250
* use we would need to improve this class to allow Basic command chaining -
* however its tricky to get right becuase there is no deliminator for Basic
* command chaining.<p>
*
* Extended commands can be chained. For example:<p>
* AT+VGM?;+VGM=14;+CIMI<p>
* This is equivalent to:<p>
* AT+VGM?
* AT+VGM=14
* AT+CIMI
* Except that only one final result code is return (although several
* intermediate responses may be returned), and as soon as one command in the
* chain fails the rest are abandonded.<p>
*
* Handlers are registered by there command name via register(Char c, ...) or
* register(String s, ...). Handlers for Basic command should be registered by
* the basic command character, and handlers for Extended commands should be
* registered by String.<p>
*
* Refer to:<ul>
* <li>ITU-T Recommendation V.250
* <li>ETSI TS 127.007 (AT Comannd set for User Equipment, 3GPP TS 27.007)
* <li>Bluetooth Headset Profile Spec (K6)
* <li>Bluetooth Handsfree Profile Spec (HFP 1.5)
* </ul>
* @hide
*/
public class AtParser {
// Extended command type enumeration, only used internally
private static final int TYPE_ACTION = 0; // AT+FOO
private static final int TYPE_READ = 1; // AT+FOO?
private static final int TYPE_SET = 2; // AT+FOO=
private static final int TYPE_TEST = 3; // AT+FOO=?
private HashMap<String, AtCommandHandler> mExtHandlers;
private HashMap<Character, AtCommandHandler> mBasicHandlers;
private String mLastInput; // for "A/" (repeat last command) support
/**
* Create a new AtParser.<p>
* No handlers are registered.
*/
public AtParser() {
mBasicHandlers = new HashMap<Character, AtCommandHandler>();
mExtHandlers = new HashMap<String, AtCommandHandler>();
mLastInput = "";
}
/**
* Register a Basic command handler.<p>
* Basic command handlers are later called via their
* <code>handleBasicCommand(String args)</code> method.
* @param command Command name - a single character
* @param handler Handler to register
*/
public void register(Character command, AtCommandHandler handler) {
mBasicHandlers.put(command, handler);
}
/**
* Register an Extended command handler.<p>
* Extended command handlers are later called via:<ul>
* <li><code>handleActionCommand()</code>
* <li><code>handleGetCommand()</code>
* <li><code>handleSetCommand()</code>
* <li><code>handleTestCommand()</code>
* </ul>
* Only one method will be called for each command processed.
* @param command Command name - can be multiple characters
* @param handler Handler to register
*/
public void register(String command, AtCommandHandler handler) {
mExtHandlers.put(command, handler);
}
/**
* Strip input of whitespace and force Uppercase - except sections inside
* quotes. Also fixes unmatched quotes (by appending a quote). Double
* quotes " are the only quotes allowed by V.250
*/
static private String clean(String input) {
StringBuilder out = new StringBuilder(input.length());
for (int i = 0; i < input.length(); i++) {
char c = input.charAt(i);
if (c == '"') {
int j = input.indexOf('"', i + 1 ); // search for closing "
if (j == -1) { // unmatched ", insert one.
out.append(input.substring(i, input.length()));
out.append('"');
break;
}
out.append(input.substring(i, j + 1));
i = j;
} else if (c != ' ') {
out.append(Character.toUpperCase(c));
}
}
return out.toString();
}
static private boolean isAtoZ(char c) {
return (c >= 'A' && c <= 'Z');
}
/**
* Find a character ch, ignoring quoted sections.
* Return input.length() if not found.
*/
static private int findChar(char ch, String input, int fromIndex) {
for (int i = fromIndex; i < input.length(); i++) {
char c = input.charAt(i);
if (c == '"') {
i = input.indexOf('"', i + 1);
if (i == -1) {
return input.length();
}
} else if (c == ch) {
return i;
}
}
return input.length();
}
/**
* Break an argument string into individual arguments (comma deliminated).
* Integer arguments are turned into Integer objects. Otherwise a String
* object is used.
*/
static private Object[] generateArgs(String input) {
int i = 0;
int j;
ArrayList<Object> out = new ArrayList<Object>();
while (i <= input.length()) {
j = findChar(',', input, i);
String arg = input.substring(i, j);
try {
out.add(new Integer(arg));
} catch (NumberFormatException e) {
out.add(arg);
}
i = j + 1; // move past comma
}
return out.toArray();
}
/**
* Return the index of the end of character after the last characeter in
* the extended command name. Uses the V.250 spec for allowed command
* names.
*/
static private int findEndExtendedName(String input, int index) {
for (int i = index; i < input.length(); i++) {
char c = input.charAt(i);
// V.250 defines the following chars as legal extended command
// names
if (isAtoZ(c)) continue;
if (c >= '0' && c <= '9') continue;
switch (c) {
case '!':
case '%':
case '-':
case '.':
case '/':
case ':':
case '_':
continue;
default:
return i;
}
}
return input.length();
}
/**
* Processes an incoming AT command line.<p>
* This method will invoke zero or one command handler methods for each
* command in the command line.<p>
* @param raw_input The AT input, without EOL deliminator (e.g. <CR>).
* @return Result object for this command line. This can be
* converted to a String[] response with toStrings().
*/
public AtCommandResult process(String raw_input) {
String input = clean(raw_input);
// Handle "A/" (repeat previous line)
if (input.regionMatches(0, "A/", 0, 2)) {
input = new String(mLastInput);
} else {
mLastInput = new String(input);
}
// Handle empty line - no response necessary
if (input.equals("")) {
// Return []
return new AtCommandResult(AtCommandResult.UNSOLICITED);
}
// Anything else deserves an error
if (!input.regionMatches(0, "AT", 0, 2)) {
// Return ["ERROR"]
return new AtCommandResult(AtCommandResult.ERROR);
}
// Ok we have a command that starts with AT. Process it
int index = 2;
AtCommandResult result =
new AtCommandResult(AtCommandResult.UNSOLICITED);
while (index < input.length()) {
char c = input.charAt(index);
if (isAtoZ(c)) {
// Option 1: Basic Command
// Pass the rest of the line as is to the handler. Do not
// look for any more commands on this line.
String args = input.substring(index + 1);
if (mBasicHandlers.containsKey((Character)c)) {
result.addResult(mBasicHandlers.get(
(Character)c).handleBasicCommand(args));
return result;
} else {
// no handler
result.addResult(
new AtCommandResult(AtCommandResult.ERROR));
return result;
}
// control never reaches here
}
if (c == '+') {
// Option 2: Extended Command
// Search for first non-name character. Shortcircuit if we dont
// handle this command name.
int i = findEndExtendedName(input, index + 1);
String commandName = input.substring(index, i);
if (!mExtHandlers.containsKey(commandName)) {
// no handler
result.addResult(
new AtCommandResult(AtCommandResult.ERROR));
return result;
}
AtCommandHandler handler = mExtHandlers.get(commandName);
// Search for end of this command - this is usually the end of
// line
int endIndex = findChar(';', input, index);
// Determine what type of command this is.
// Default to TYPE_ACTION if we can't find anything else
// obvious.
int type;
if (i >= endIndex) {
type = TYPE_ACTION;
} else if (input.charAt(i) == '?') {
type = TYPE_READ;
} else if (input.charAt(i) == '=') {
if (i + 1 < endIndex) {
if (input.charAt(i + 1) == '?') {
type = TYPE_TEST;
} else {
type = TYPE_SET;
}
} else {
type = TYPE_SET;
}
} else {
type = TYPE_ACTION;
}
// Call this command. Short-circuit as soon as a command fails
switch (type) {
case TYPE_ACTION:
result.addResult(handler.handleActionCommand());
break;
case TYPE_READ:
result.addResult(handler.handleReadCommand());
break;
case TYPE_TEST:
result.addResult(handler.handleTestCommand());
break;
case TYPE_SET:
Object[] args =
generateArgs(input.substring(i + 1, endIndex));
result.addResult(handler.handleSetCommand(args));
break;
}
if (result.getResultCode() != AtCommandResult.OK) {
return result; // short-circuit
}
index = endIndex;
} else {
// Can't tell if this is a basic or extended command.
// Push forwards and hope we hit something.
index++;
}
}
// Finished processing (and all results were ok)
return result;
}
}