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