| /* |
| * 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; |
| } |
| } |