Home | History | Annotate | Download | only in bluetooth
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.bluetooth;
     18 
     19 import java.util.*;
     20 
     21 /**
     22  * An AT (Hayes command) Parser based on (a subset of) the ITU-T V.250 standard.
     23  * <p>
     24  *
     25  * Conformant with the subset of V.250 required for implementation of the
     26  * Bluetooth Headset and Handsfree Profiles, as per Bluetooth SIP
     27  * specifications. Also implements some V.250 features not required by
     28  * Bluetooth - such as chained commands.<p>
     29  *
     30  * Command handlers are registered with an AtParser object. These handlers are
     31  * invoked when command lines are processed by AtParser's process() method.<p>
     32  *
     33  * The AtParser object accepts a new command line to parse via its process()
     34  * method. It breaks each command line into one or more commands. Each command
     35  * is parsed for name, type, and (optional) arguments, and an appropriate
     36  * external handler method is called through the AtCommandHandler interface.
     37  *
     38  * The command types are<ul>
     39  * <li>Basic Command. For example "ATDT1234567890". Basic command names are a
     40  * single character (e.g. "D"), and everything following this character is
     41  * passed to the handler as a string argument (e.g. "T1234567890").
     42  * <li>Action Command. For example "AT+CIMI". The command name is "CIMI", and
     43  * there are no arguments for action commands.
     44  * <li>Read Command. For example "AT+VGM?". The command name is "VGM", and there
     45  * are no arguments for get commands.
     46  * <li>Set Command. For example "AT+VGM=14". The command name is "VGM", and
     47  * there is a single integer argument in this case. In the general case then
     48  * can be zero or more arguments (comma delimited) each of integer or string
     49  * form.
     50  * <li>Test Command. For example "AT+VGM=?. No arguments.
     51  * </ul>
     52  *
     53  * In V.250 the last four command types are known as Extended Commands, and
     54  * they are used heavily in Bluetooth.<p>
     55  *
     56  * Basic commands cannot be chained in this implementation. For Bluetooth
     57  * headset/handsfree use this is acceptable, because they only use the basic
     58  * commands ATA and ATD, which are not allowed to be chained. For general V.250
     59  * use we would need to improve this class to allow Basic command chaining -
     60  * however it's tricky to get right because there is no delimiter for Basic
     61  * command chaining.<p>
     62  *
     63  * Extended commands can be chained. For example:<p>
     64  * AT+VGM?;+VGM=14;+CIMI<p>
     65  * This is equivalent to:<p>
     66  * AT+VGM?
     67  * AT+VGM=14
     68  * AT+CIMI
     69  * Except that only one final result code is return (although several
     70  * intermediate responses may be returned), and as soon as one command in the
     71  * chain fails the rest are abandoned.<p>
     72  *
     73  * Handlers are registered by there command name via register(Char c, ...) or
     74  * register(String s, ...). Handlers for Basic command should be registered by
     75  * the basic command character, and handlers for Extended commands should be
     76  * registered by String.<p>
     77  *
     78  * Refer to:<ul>
     79  * <li>ITU-T Recommendation V.250
     80  * <li>ETSI TS 127.007  (AT Command set for User Equipment, 3GPP TS 27.007)
     81  * <li>Bluetooth Headset Profile Spec (K6)
     82  * <li>Bluetooth Handsfree Profile Spec (HFP 1.5)
     83  * </ul>
     84  * @hide
     85  */
     86 public class AtParser {
     87 
     88     // Extended command type enumeration, only used internally
     89     private static final int TYPE_ACTION = 0;   // AT+FOO
     90     private static final int TYPE_READ = 1;     // AT+FOO?
     91     private static final int TYPE_SET = 2;      // AT+FOO=
     92     private static final int TYPE_TEST = 3;     // AT+FOO=?
     93 
     94     private HashMap<String, AtCommandHandler> mExtHandlers;
     95     private HashMap<Character, AtCommandHandler> mBasicHandlers;
     96 
     97     private String mLastInput;  // for "A/" (repeat last command) support
     98 
     99     /**
    100      * Create a new AtParser.<p>
    101      * No handlers are registered.
    102      */
    103     public AtParser() {
    104         mBasicHandlers = new HashMap<Character, AtCommandHandler>();
    105         mExtHandlers = new HashMap<String, AtCommandHandler>();
    106         mLastInput = "";
    107     }
    108 
    109     /**
    110      * Register a Basic command handler.<p>
    111      * Basic command handlers are later called via their
    112      * <code>handleBasicCommand(String args)</code> method.
    113      * @param  command Command name - a single character
    114      * @param  handler Handler to register
    115      */
    116     public void register(Character command, AtCommandHandler handler) {
    117         mBasicHandlers.put(command, handler);
    118     }
    119 
    120     /**
    121      * Register an Extended command handler.<p>
    122      * Extended command handlers are later called via:<ul>
    123      * <li><code>handleActionCommand()</code>
    124      * <li><code>handleGetCommand()</code>
    125      * <li><code>handleSetCommand()</code>
    126      * <li><code>handleTestCommand()</code>
    127      * </ul>
    128      * Only one method will be called for each command processed.
    129      * @param  command Command name - can be multiple characters
    130      * @param  handler Handler to register
    131      */
    132     public void register(String command, AtCommandHandler handler) {
    133         mExtHandlers.put(command, handler);
    134     }
    135 
    136 
    137     /**
    138      * Strip input of whitespace and force Uppercase - except sections inside
    139      * quotes. Also fixes unmatched quotes (by appending a quote). Double
    140      * quotes " are the only quotes allowed by V.250
    141      */
    142     static private String clean(String input) {
    143         StringBuilder out = new StringBuilder(input.length());
    144 
    145         for (int i = 0; i < input.length(); i++) {
    146             char c = input.charAt(i);
    147             if (c == '"') {
    148                 int j = input.indexOf('"', i + 1 );  // search for closing "
    149                 if (j == -1) {  // unmatched ", insert one.
    150                     out.append(input.substring(i, input.length()));
    151                     out.append('"');
    152                     break;
    153                 }
    154                 out.append(input.substring(i, j + 1));
    155                 i = j;
    156             } else if (c != ' ') {
    157                 out.append(Character.toUpperCase(c));
    158             }
    159         }
    160 
    161         return out.toString();
    162     }
    163 
    164     static private boolean isAtoZ(char c) {
    165         return (c >= 'A' && c <= 'Z');
    166     }
    167 
    168     /**
    169      * Find a character ch, ignoring quoted sections.
    170      * Return input.length() if not found.
    171      */
    172     static private int findChar(char ch, String input, int fromIndex) {
    173         for (int i = fromIndex; i < input.length(); i++) {
    174             char c = input.charAt(i);
    175             if (c == '"') {
    176                 i = input.indexOf('"', i + 1);
    177                 if (i == -1) {
    178                     return input.length();
    179                 }
    180             } else if (c == ch) {
    181                 return i;
    182             }
    183         }
    184         return input.length();
    185     }
    186 
    187     /**
    188      * Break an argument string into individual arguments (comma delimited).
    189      * Integer arguments are turned into Integer objects. Otherwise a String
    190      * object is used.
    191      */
    192     static private Object[] generateArgs(String input) {
    193         int i = 0;
    194         int j;
    195         ArrayList<Object> out = new ArrayList<Object>();
    196         while (i <= input.length()) {
    197             j = findChar(',', input, i);
    198 
    199             String arg = input.substring(i, j);
    200             try {
    201                 out.add(new Integer(arg));
    202             } catch (NumberFormatException e) {
    203                 out.add(arg);
    204             }
    205 
    206             i = j + 1; // move past comma
    207         }
    208         return out.toArray();
    209     }
    210 
    211     /**
    212      * Return the index of the end of character after the last character in
    213      * the extended command name. Uses the V.250 spec for allowed command
    214      * names.
    215      */
    216     static private int findEndExtendedName(String input, int index) {
    217         for (int i = index; i < input.length(); i++) {
    218             char c = input.charAt(i);
    219 
    220             // V.250 defines the following chars as legal extended command
    221             // names
    222             if (isAtoZ(c)) continue;
    223             if (c >= '0' && c <= '9') continue;
    224             switch (c) {
    225             case '!':
    226             case '%':
    227             case '-':
    228             case '.':
    229             case '/':
    230             case ':':
    231             case '_':
    232                 continue;
    233             default:
    234                 return i;
    235             }
    236         }
    237         return input.length();
    238     }
    239 
    240     /**
    241      * Processes an incoming AT command line.<p>
    242      * This method will invoke zero or one command handler methods for each
    243      * command in the command line.<p>
    244      * @param raw_input The AT input, without EOL delimiter (e.g. <CR>).
    245      * @return          Result object for this command line. This can be
    246      *                  converted to a String[] response with toStrings().
    247      */
    248     public AtCommandResult process(String raw_input) {
    249         String input = clean(raw_input);
    250 
    251         // Handle "A/" (repeat previous line)
    252         if (input.regionMatches(0, "A/", 0, 2)) {
    253             input = new String(mLastInput);
    254         } else {
    255             mLastInput = new String(input);
    256         }
    257 
    258         // Handle empty line - no response necessary
    259         if (input.equals("")) {
    260             // Return []
    261             return new AtCommandResult(AtCommandResult.UNSOLICITED);
    262         }
    263 
    264         // Anything else deserves an error
    265         if (!input.regionMatches(0, "AT", 0, 2)) {
    266             // Return ["ERROR"]
    267             return new AtCommandResult(AtCommandResult.ERROR);
    268         }
    269 
    270         // Ok we have a command that starts with AT. Process it
    271         int index = 2;
    272         AtCommandResult result =
    273                 new AtCommandResult(AtCommandResult.UNSOLICITED);
    274         while (index < input.length()) {
    275             char c = input.charAt(index);
    276 
    277             if (isAtoZ(c)) {
    278                 // Option 1: Basic Command
    279                 // Pass the rest of the line as is to the handler. Do not
    280                 // look for any more commands on this line.
    281                 String args = input.substring(index + 1);
    282                 if (mBasicHandlers.containsKey((Character)c)) {
    283                     result.addResult(mBasicHandlers.get(
    284                             (Character)c).handleBasicCommand(args));
    285                     return result;
    286                 } else {
    287                     // no handler
    288                     result.addResult(
    289                             new AtCommandResult(AtCommandResult.ERROR));
    290                     return result;
    291                 }
    292                 // control never reaches here
    293             }
    294 
    295             if (c == '+') {
    296                 // Option 2: Extended Command
    297                 // Search for first non-name character. Short-circuit if
    298                 // we don't handle this command name.
    299                 int i = findEndExtendedName(input, index + 1);
    300                 String commandName = input.substring(index, i);
    301                 if (!mExtHandlers.containsKey(commandName)) {
    302                     // no handler
    303                     result.addResult(
    304                             new AtCommandResult(AtCommandResult.ERROR));
    305                     return result;
    306                 }
    307                 AtCommandHandler handler = mExtHandlers.get(commandName);
    308 
    309                 // Search for end of this command - this is usually the end of
    310                 // line
    311                 int endIndex = findChar(';', input, index);
    312 
    313                 // Determine what type of command this is.
    314                 // Default to TYPE_ACTION if we can't find anything else
    315                 // obvious.
    316                 int type;
    317 
    318                 if (i >= endIndex) {
    319                     type = TYPE_ACTION;
    320                 } else if (input.charAt(i) == '?') {
    321                     type = TYPE_READ;
    322                 } else if (input.charAt(i) == '=') {
    323                     if (i + 1 < endIndex) {
    324                         if (input.charAt(i + 1) == '?') {
    325                             type = TYPE_TEST;
    326                         } else {
    327                             type = TYPE_SET;
    328                         }
    329                     } else {
    330                         type = TYPE_SET;
    331                     }
    332                 } else {
    333                     type = TYPE_ACTION;
    334                 }
    335 
    336                 // Call this command. Short-circuit as soon as a command fails
    337                 switch (type) {
    338                 case TYPE_ACTION:
    339                     result.addResult(handler.handleActionCommand());
    340                     break;
    341                 case TYPE_READ:
    342                     result.addResult(handler.handleReadCommand());
    343                     break;
    344                 case TYPE_TEST:
    345                     result.addResult(handler.handleTestCommand());
    346                     break;
    347                 case TYPE_SET:
    348                     Object[] args =
    349                             generateArgs(input.substring(i + 1, endIndex));
    350                     result.addResult(handler.handleSetCommand(args));
    351                     break;
    352                 }
    353                 if (result.getResultCode() != AtCommandResult.OK) {
    354                     return result;   // short-circuit
    355                 }
    356 
    357                 index = endIndex;
    358             } else {
    359                 // Can't tell if this is a basic or extended command.
    360                 // Push forwards and hope we hit something.
    361                 index++;
    362             }
    363         }
    364         // Finished processing (and all results were ok)
    365         return result;
    366     }
    367 }
    368