• 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 com.android.ddmlib;
18 
19 import java.io.IOException;
20 import java.io.UnsupportedEncodingException;
21 import java.net.InetAddress;
22 import java.net.InetSocketAddress;
23 import java.net.UnknownHostException;
24 import java.nio.ByteBuffer;
25 import java.nio.channels.SocketChannel;
26 import java.security.InvalidParameterException;
27 import java.util.Formatter;
28 import java.util.HashMap;
29 import java.util.Locale;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32 
33 /**
34  * Provides control over emulated hardware of the Android emulator.
35  * <p/>This is basically a wrapper around the command line console normally used with telnet.
36  *<p/>
37  * Regarding line termination handling:<br>
38  * One of the issues is that the telnet protocol <b>requires</b> usage of <code>\r\n</code>. Most
39  * implementations don't enforce it (the dos one does). In this particular case, this is mostly
40  * irrelevant since we don't use telnet in Java, but that means we want to make
41  * sure we use the same line termination than what the console expects. The console
42  * code removes <code>\r</code> and waits for <code>\n</code>.
43  * <p/>However this means you <i>may</i> receive <code>\r\n</code> when reading from the console.
44  * <p/>
45  * <b>This API will change in the near future.</b>
46  */
47 public final class EmulatorConsole {
48 
49     private final static String DEFAULT_ENCODING = "ISO-8859-1"; //$NON-NLS-1$
50 
51     private final static int WAIT_TIME = 5; // spin-wait sleep, in ms
52 
53     private final static int STD_TIMEOUT = 5000; // standard delay, in ms
54 
55     private final static String HOST = "127.0.0.1";  //$NON-NLS-1$
56 
57     private final static String COMMAND_PING = "help\r\n"; //$NON-NLS-1$
58     private final static String COMMAND_AVD_NAME = "avd name\r\n"; //$NON-NLS-1$
59     private final static String COMMAND_KILL = "kill\r\n"; //$NON-NLS-1$
60     private final static String COMMAND_GSM_STATUS = "gsm status\r\n"; //$NON-NLS-1$
61     private final static String COMMAND_GSM_CALL = "gsm call %1$s\r\n"; //$NON-NLS-1$
62     private final static String COMMAND_GSM_CANCEL_CALL = "gsm cancel %1$s\r\n"; //$NON-NLS-1$
63     private final static String COMMAND_GSM_DATA = "gsm data %1$s\r\n"; //$NON-NLS-1$
64     private final static String COMMAND_GSM_VOICE = "gsm voice %1$s\r\n"; //$NON-NLS-1$
65     private final static String COMMAND_SMS_SEND = "sms send %1$s %2$s\r\n"; //$NON-NLS-1$
66     private final static String COMMAND_NETWORK_STATUS = "network status\r\n"; //$NON-NLS-1$
67     private final static String COMMAND_NETWORK_SPEED = "network speed %1$s\r\n"; //$NON-NLS-1$
68     private final static String COMMAND_NETWORK_LATENCY = "network delay %1$s\r\n"; //$NON-NLS-1$
69     private final static String COMMAND_GPS = "geo fix %1$f %2$f %3$f\r\n"; //$NON-NLS-1$
70 
71     private final static Pattern RE_KO = Pattern.compile("KO:\\s+(.*)"); //$NON-NLS-1$
72 
73     /**
74      * Array of delay values: no delay, gprs, edge/egprs, umts/3d
75      */
76     public final static int[] MIN_LATENCIES = new int[] {
77         0,      // No delay
78         150,    // gprs
79         80,     // edge/egprs
80         35      // umts/3g
81     };
82 
83     /**
84      * Array of download speeds: full speed, gsm, hscsd, gprs, edge/egprs, umts/3g, hsdpa.
85      */
86     public final int[] DOWNLOAD_SPEEDS = new int[] {
87         0,          // full speed
88         14400,      // gsm
89         43200,      // hscsd
90         80000,      // gprs
91         236800,     // edge/egprs
92         1920000,    // umts/3g
93         14400000    // hsdpa
94     };
95 
96     /** Arrays of valid network speeds */
97     public final static String[] NETWORK_SPEEDS = new String[] {
98         "full", //$NON-NLS-1$
99         "gsm", //$NON-NLS-1$
100         "hscsd", //$NON-NLS-1$
101         "gprs", //$NON-NLS-1$
102         "edge", //$NON-NLS-1$
103         "umts", //$NON-NLS-1$
104         "hsdpa", //$NON-NLS-1$
105     };
106 
107     /** Arrays of valid network latencies */
108     public final static String[] NETWORK_LATENCIES = new String[] {
109         "none", //$NON-NLS-1$
110         "gprs", //$NON-NLS-1$
111         "edge", //$NON-NLS-1$
112         "umts", //$NON-NLS-1$
113     };
114 
115     /** Gsm Mode enum. */
116     public static enum GsmMode {
117         UNKNOWN((String)null),
118         UNREGISTERED(new String[] { "unregistered", "off" }),
119         HOME(new String[] { "home", "on" }),
120         ROAMING("roaming"),
121         SEARCHING("searching"),
122         DENIED("denied");
123 
124         private final String[] tags;
125 
GsmMode(String tag)126         GsmMode(String tag) {
127             if (tag != null) {
128                 this.tags = new String[] { tag };
129             } else {
130                 this.tags = new String[0];
131             }
132         }
133 
GsmMode(String[] tags)134         GsmMode(String[] tags) {
135             this.tags = tags;
136         }
137 
getEnum(String tag)138         public static GsmMode getEnum(String tag) {
139             for (GsmMode mode : values()) {
140                 for (String t : mode.tags) {
141                     if (t.equals(tag)) {
142                         return mode;
143                     }
144                 }
145             }
146             return UNKNOWN;
147         }
148 
149         /**
150          * Returns the first tag of the enum.
151          */
getTag()152         public String getTag() {
153             if (tags.length > 0) {
154                 return tags[0];
155             }
156             return null;
157         }
158     }
159 
160     public final static String RESULT_OK = null;
161 
162     private final static Pattern sEmulatorRegexp = Pattern.compile(Device.RE_EMULATOR_SN);
163     private final static Pattern sVoiceStatusRegexp = Pattern.compile(
164             "gsm\\s+voice\\s+state:\\s*([a-z]+)", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
165     private final static Pattern sDataStatusRegexp = Pattern.compile(
166             "gsm\\s+data\\s+state:\\s*([a-z]+)", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
167     private final static Pattern sDownloadSpeedRegexp = Pattern.compile(
168             "\\s+download\\s+speed:\\s+(\\d+)\\s+bits.*", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
169     private final static Pattern sMinLatencyRegexp = Pattern.compile(
170             "\\s+minimum\\s+latency:\\s+(\\d+)\\s+ms", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
171 
172     private final static HashMap<Integer, EmulatorConsole> sEmulators =
173         new HashMap<Integer, EmulatorConsole>();
174 
175     /** Gsm Status class */
176     public static class GsmStatus {
177         /** Voice status. */
178         public GsmMode voice = GsmMode.UNKNOWN;
179         /** Data status. */
180         public GsmMode data = GsmMode.UNKNOWN;
181     }
182 
183     /** Network Status class */
184     public static class NetworkStatus {
185         /** network speed status. This is an index in the {@link #DOWNLOAD_SPEEDS} array. */
186         public int speed = -1;
187         /** network latency status.  This is an index in the {@link #MIN_LATENCIES} array. */
188         public int latency = -1;
189     }
190 
191     private int mPort;
192 
193     private SocketChannel mSocketChannel;
194 
195     private byte[] mBuffer = new byte[1024];
196 
197     /**
198      * Returns an {@link EmulatorConsole} object for the given {@link Device}. This can
199      * be an already existing console, or a new one if it hadn't been created yet.
200      * @param d The device that the console links to.
201      * @return an <code>EmulatorConsole</code> object or <code>null</code> if the connection failed.
202      */
getConsole(IDevice d)203     public static synchronized EmulatorConsole getConsole(IDevice d) {
204         // we need to make sure that the device is an emulator
205         // get the port number. This is the console port.
206         Integer port = getEmulatorPort(d.getSerialNumber());
207         if (port == null) {
208             return null;
209         }
210 
211         EmulatorConsole console = sEmulators.get(port);
212 
213         if (console != null) {
214             // if the console exist, we ping the emulator to check the connection.
215             if (console.ping() == false) {
216                 RemoveConsole(console.mPort);
217                 console = null;
218             }
219         }
220 
221         if (console == null) {
222             // no console object exists for this port so we create one, and start
223             // the connection.
224             console = new EmulatorConsole(port);
225             if (console.start()) {
226                 sEmulators.put(port, console);
227             } else {
228                 console = null;
229             }
230         }
231 
232         return console;
233     }
234 
235     /**
236      * Return port of emulator given its serial number.
237      *
238      * @param serialNumber the emulator's serial number
239      * @return the integer port or <code>null</code> if it could not be determined
240      */
getEmulatorPort(String serialNumber)241     public static Integer getEmulatorPort(String serialNumber) {
242         Matcher m = sEmulatorRegexp.matcher(serialNumber);
243         if (m.matches()) {
244             // get the port number. This is the console port.
245             int port;
246             try {
247                 port = Integer.parseInt(m.group(1));
248                 if (port > 0) {
249                     return port;
250                 }
251             } catch (NumberFormatException e) {
252                 // looks like we failed to get the port number. This is a bit strange since
253                 // it's coming from a regexp that only accept digit, but we handle the case
254                 // and return null.
255             }
256         }
257         return null;
258     }
259 
260     /**
261      * Removes the console object associated with a port from the map.
262      * @param port The port of the console to remove.
263      */
RemoveConsole(int port)264     private static synchronized void RemoveConsole(int port) {
265         sEmulators.remove(port);
266     }
267 
EmulatorConsole(int port)268     private EmulatorConsole(int port) {
269         super();
270         mPort = port;
271     }
272 
273     /**
274      * Starts the connection of the console.
275      * @return true if success.
276      */
start()277     private boolean start() {
278 
279         InetSocketAddress socketAddr;
280         try {
281             InetAddress hostAddr = InetAddress.getByName(HOST);
282             socketAddr = new InetSocketAddress(hostAddr, mPort);
283         } catch (UnknownHostException e) {
284             return false;
285         }
286 
287         try {
288             mSocketChannel = SocketChannel.open(socketAddr);
289         } catch (IOException e1) {
290             return false;
291         }
292 
293         // read some stuff from it
294         readLines();
295 
296         return true;
297     }
298 
299     /**
300      * Ping the emulator to check if the connection is still alive.
301      * @return true if the connection is alive.
302      */
ping()303     private synchronized boolean ping() {
304         // it looks like we can send stuff, even when the emulator quit, but we can't read
305         // from the socket. So we check the return of readLines()
306         if (sendCommand(COMMAND_PING)) {
307             return readLines() != null;
308         }
309 
310         return false;
311     }
312 
313     /**
314      * Sends a KILL command to the emulator.
315      */
kill()316     public synchronized void kill() {
317         if (sendCommand(COMMAND_KILL)) {
318             RemoveConsole(mPort);
319         }
320     }
321 
getAvdName()322     public synchronized String getAvdName() {
323         if (sendCommand(COMMAND_AVD_NAME)) {
324             String[] result = readLines();
325             if (result != null && result.length == 2) { // this should be the name on first line,
326                                                         // and ok on 2nd line
327                 return result[0];
328             } else {
329                 // try to see if there's a message after KO
330                 Matcher m = RE_KO.matcher(result[result.length-1]);
331                 if (m.matches()) {
332                     return m.group(1);
333                 }
334             }
335         }
336 
337         return null;
338     }
339 
340     /**
341      * Get the network status of the emulator.
342      * @return a {@link NetworkStatus} object containing the {@link GsmStatus}, or
343      * <code>null</code> if the query failed.
344      */
getNetworkStatus()345     public synchronized NetworkStatus getNetworkStatus() {
346         if (sendCommand(COMMAND_NETWORK_STATUS)) {
347             /* Result is in the format
348                 Current network status:
349                 download speed:      14400 bits/s (1.8 KB/s)
350                 upload speed:        14400 bits/s (1.8 KB/s)
351                 minimum latency:  0 ms
352                 maximum latency:  0 ms
353              */
354             String[] result = readLines();
355 
356             if (isValid(result)) {
357                 // we only compare agains the min latency and the download speed
358                 // let's not rely on the order of the output, and simply loop through
359                 // the line testing the regexp.
360                 NetworkStatus status = new NetworkStatus();
361                 for (String line : result) {
362                     Matcher m = sDownloadSpeedRegexp.matcher(line);
363                     if (m.matches()) {
364                         // get the string value
365                         String value = m.group(1);
366 
367                         // get the index from the list
368                         status.speed = getSpeedIndex(value);
369 
370                         // move on to next line.
371                         continue;
372                     }
373 
374                     m = sMinLatencyRegexp.matcher(line);
375                     if (m.matches()) {
376                         // get the string value
377                         String value = m.group(1);
378 
379                         // get the index from the list
380                         status.latency = getLatencyIndex(value);
381 
382                         // move on to next line.
383                         continue;
384                     }
385                 }
386 
387                 return status;
388             }
389         }
390 
391         return null;
392     }
393 
394     /**
395      * Returns the current gsm status of the emulator
396      * @return a {@link GsmStatus} object containing the gms status, or <code>null</code>
397      * if the query failed.
398      */
getGsmStatus()399     public synchronized GsmStatus getGsmStatus() {
400         if (sendCommand(COMMAND_GSM_STATUS)) {
401             /*
402              * result is in the format:
403              * gsm status
404              * gsm voice state: home
405              * gsm data state:  home
406              */
407 
408             String[] result = readLines();
409             if (isValid(result)) {
410 
411                 GsmStatus status = new GsmStatus();
412 
413                 // let's not rely on the order of the output, and simply loop through
414                 // the line testing the regexp.
415                 for (String line : result) {
416                     Matcher m = sVoiceStatusRegexp.matcher(line);
417                     if (m.matches()) {
418                         // get the string value
419                         String value = m.group(1);
420 
421                         // get the index from the list
422                         status.voice = GsmMode.getEnum(value.toLowerCase(Locale.US));
423 
424                         // move on to next line.
425                         continue;
426                     }
427 
428                     m = sDataStatusRegexp.matcher(line);
429                     if (m.matches()) {
430                         // get the string value
431                         String value = m.group(1);
432 
433                         // get the index from the list
434                         status.data = GsmMode.getEnum(value.toLowerCase(Locale.US));
435 
436                         // move on to next line.
437                         continue;
438                     }
439                 }
440 
441                 return status;
442             }
443         }
444 
445         return null;
446     }
447 
448     /**
449      * Sets the GSM voice mode.
450      * @param mode the {@link GsmMode} value.
451      * @return RESULT_OK if success, an error String otherwise.
452      * @throws InvalidParameterException if mode is an invalid value.
453      */
setGsmVoiceMode(GsmMode mode)454     public synchronized String setGsmVoiceMode(GsmMode mode) throws InvalidParameterException {
455         if (mode == GsmMode.UNKNOWN) {
456             throw new InvalidParameterException();
457         }
458 
459         String command = String.format(COMMAND_GSM_VOICE, mode.getTag());
460         return processCommand(command);
461     }
462 
463     /**
464      * Sets the GSM data mode.
465      * @param mode the {@link GsmMode} value
466      * @return {@link #RESULT_OK} if success, an error String otherwise.
467      * @throws InvalidParameterException if mode is an invalid value.
468      */
setGsmDataMode(GsmMode mode)469     public synchronized String setGsmDataMode(GsmMode mode) throws InvalidParameterException {
470         if (mode == GsmMode.UNKNOWN) {
471             throw new InvalidParameterException();
472         }
473 
474         String command = String.format(COMMAND_GSM_DATA, mode.getTag());
475         return processCommand(command);
476     }
477 
478     /**
479      * Initiate an incoming call on the emulator.
480      * @param number a string representing the calling number.
481      * @return {@link #RESULT_OK} if success, an error String otherwise.
482      */
call(String number)483     public synchronized String call(String number) {
484         String command = String.format(COMMAND_GSM_CALL, number);
485         return processCommand(command);
486     }
487 
488     /**
489      * Cancels a current call.
490      * @param number the number of the call to cancel
491      * @return {@link #RESULT_OK} if success, an error String otherwise.
492      */
cancelCall(String number)493     public synchronized String cancelCall(String number) {
494         String command = String.format(COMMAND_GSM_CANCEL_CALL, number);
495         return processCommand(command);
496     }
497 
498     /**
499      * Sends an SMS to the emulator
500      * @param number The sender phone number
501      * @param message The SMS message. \ characters must be escaped. The carriage return is
502      * the 2 character sequence  {'\', 'n' }
503      *
504      * @return {@link #RESULT_OK} if success, an error String otherwise.
505      */
sendSms(String number, String message)506     public synchronized String sendSms(String number, String message) {
507         String command = String.format(COMMAND_SMS_SEND, number, message);
508         return processCommand(command);
509     }
510 
511     /**
512      * Sets the network speed.
513      * @param selectionIndex The index in the {@link #NETWORK_SPEEDS} table.
514      * @return {@link #RESULT_OK} if success, an error String otherwise.
515      */
setNetworkSpeed(int selectionIndex)516     public synchronized String setNetworkSpeed(int selectionIndex) {
517         String command = String.format(COMMAND_NETWORK_SPEED, NETWORK_SPEEDS[selectionIndex]);
518         return processCommand(command);
519     }
520 
521     /**
522      * Sets the network latency.
523      * @param selectionIndex The index in the {@link #NETWORK_LATENCIES} table.
524      * @return {@link #RESULT_OK} if success, an error String otherwise.
525      */
setNetworkLatency(int selectionIndex)526     public synchronized String setNetworkLatency(int selectionIndex) {
527         String command = String.format(COMMAND_NETWORK_LATENCY, NETWORK_LATENCIES[selectionIndex]);
528         return processCommand(command);
529     }
530 
sendLocation(double longitude, double latitude, double elevation)531     public synchronized String sendLocation(double longitude, double latitude, double elevation) {
532 
533         // need to make sure the string format uses dot and not comma
534         Formatter formatter = new Formatter(Locale.US);
535         formatter.format(COMMAND_GPS, longitude, latitude, elevation);
536 
537         return processCommand(formatter.toString());
538     }
539 
540     /**
541      * Sends a command to the emulator console.
542      * @param command The command string. <b>MUST BE TERMINATED BY \n</b>.
543      * @return true if success
544      */
sendCommand(String command)545     private boolean sendCommand(String command) {
546         boolean result = false;
547         try {
548             byte[] bCommand;
549             try {
550                 bCommand = command.getBytes(DEFAULT_ENCODING);
551             } catch (UnsupportedEncodingException e) {
552                 // wrong encoding...
553                 return result;
554             }
555 
556             // write the command
557             AdbHelper.write(mSocketChannel, bCommand, bCommand.length, DdmPreferences.getTimeOut());
558 
559             result = true;
560         } catch (Exception e) {
561             return false;
562         } finally {
563             if (result == false) {
564                 // FIXME connection failed somehow, we need to disconnect the console.
565                 RemoveConsole(mPort);
566             }
567         }
568 
569         return result;
570     }
571 
572     /**
573      * Sends a command to the emulator and parses its answer.
574      * @param command the command to send.
575      * @return {@link #RESULT_OK} if success, an error message otherwise.
576      */
processCommand(String command)577     private String processCommand(String command) {
578         if (sendCommand(command)) {
579             String[] result = readLines();
580 
581             if (result != null && result.length > 0) {
582                 Matcher m = RE_KO.matcher(result[result.length-1]);
583                 if (m.matches()) {
584                     return m.group(1);
585                 }
586                 return RESULT_OK;
587             }
588 
589             return "Unable to communicate with the emulator";
590         }
591 
592         return "Unable to send command to the emulator";
593     }
594 
595     /**
596      * Reads line from the console socket. This call is blocking until we read the lines:
597      * <ul>
598      * <li>OK\r\n</li>
599      * <li>KO<msg>\r\n</li>
600      * </ul>
601      * @return the array of strings read from the emulator.
602      */
readLines()603     private String[] readLines() {
604         try {
605             ByteBuffer buf = ByteBuffer.wrap(mBuffer, 0, mBuffer.length);
606             int numWaits = 0;
607             boolean stop = false;
608 
609             while (buf.position() != buf.limit() && stop == false) {
610                 int count;
611 
612                 count = mSocketChannel.read(buf);
613                 if (count < 0) {
614                     return null;
615                 } else if (count == 0) {
616                     if (numWaits * WAIT_TIME > STD_TIMEOUT) {
617                         return null;
618                     }
619                     // non-blocking spin
620                     try {
621                         Thread.sleep(WAIT_TIME);
622                     } catch (InterruptedException ie) {
623                     }
624                     numWaits++;
625                 } else {
626                     numWaits = 0;
627                 }
628 
629                 // check the last few char aren't OK. For a valid message to test
630                 // we need at least 4 bytes (OK/KO + \r\n)
631                 if (buf.position() >= 4) {
632                     int pos = buf.position();
633                     if (endsWithOK(pos) || lastLineIsKO(pos)) {
634                         stop = true;
635                     }
636                 }
637             }
638 
639             String msg = new String(mBuffer, 0, buf.position(), DEFAULT_ENCODING);
640             return msg.split("\r\n"); //$NON-NLS-1$
641         } catch (IOException e) {
642             return null;
643         }
644     }
645 
646     /**
647      * Returns true if the 4 characters *before* the current position are "OK\r\n"
648      * @param currentPosition The current position
649      */
endsWithOK(int currentPosition)650     private boolean endsWithOK(int currentPosition) {
651         if (mBuffer[currentPosition-1] == '\n' &&
652                 mBuffer[currentPosition-2] == '\r' &&
653                 mBuffer[currentPosition-3] == 'K' &&
654                 mBuffer[currentPosition-4] == 'O') {
655             return true;
656         }
657 
658         return false;
659     }
660 
661     /**
662      * Returns true if the last line starts with KO and is also terminated by \r\n
663      * @param currentPosition the current position
664      */
lastLineIsKO(int currentPosition)665     private boolean lastLineIsKO(int currentPosition) {
666         // first check that the last 2 characters are CRLF
667         if (mBuffer[currentPosition-1] != '\n' ||
668                 mBuffer[currentPosition-2] != '\r') {
669             return false;
670         }
671 
672         // now loop backward looking for the previous CRLF, or the beginning of the buffer
673         int i = 0;
674         for (i = currentPosition-3 ; i >= 0; i--) {
675             if (mBuffer[i] == '\n') {
676                 // found \n!
677                 if (i > 0 && mBuffer[i-1] == '\r') {
678                     // found \r!
679                     break;
680                 }
681             }
682         }
683 
684         // here it is either -1 if we reached the start of the buffer without finding
685         // a CRLF, or the position of \n. So in both case we look at the characters at i+1 and i+2
686         if (mBuffer[i+1] == 'K' && mBuffer[i+2] == 'O') {
687             // found error!
688             return true;
689         }
690 
691         return false;
692     }
693 
694     /**
695      * Returns true if the last line of the result does not start with KO
696      */
isValid(String[] result)697     private boolean isValid(String[] result) {
698         if (result != null && result.length > 0) {
699             return !(RE_KO.matcher(result[result.length-1]).matches());
700         }
701         return false;
702     }
703 
getLatencyIndex(String value)704     private int getLatencyIndex(String value) {
705         try {
706             // get the int value
707             int latency = Integer.parseInt(value);
708 
709             // check for the speed from the index
710             for (int i = 0 ; i < MIN_LATENCIES.length; i++) {
711                 if (MIN_LATENCIES[i] == latency) {
712                     return i;
713                 }
714             }
715         } catch (NumberFormatException e) {
716             // Do nothing, we'll just return -1.
717         }
718 
719         return -1;
720     }
721 
getSpeedIndex(String value)722     private int getSpeedIndex(String value) {
723         try {
724             // get the int value
725             int speed = Integer.parseInt(value);
726 
727             // check for the speed from the index
728             for (int i = 0 ; i < DOWNLOAD_SPEEDS.length; i++) {
729                 if (DOWNLOAD_SPEEDS[i] == speed) {
730                     return i;
731                 }
732             }
733         } catch (NumberFormatException e) {
734             // Do nothing, we'll just return -1.
735         }
736 
737         return -1;
738     }
739 }
740