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