• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
AtParser()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      */
register(Character command, AtCommandHandler handler)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      */
register(String command, AtCommandHandler handler)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      */
clean(String input)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 
isAtoZ(char c)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      */
findChar(char ch, String input, int fromIndex)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      */
generateArgs(String input)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      */
findEndExtendedName(String input, int index)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      */
process(String raw_input)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