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