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