Home | History | Annotate | Download | only in ddmlib
      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 
    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 
    134         GsmMode(String[] tags) {
    135             this.tags = tags;
    136         }
    137 
    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          */
    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      */
    203     public static synchronized EmulatorConsole getConsole(IDevice d) {
    204         // we need to make sure that the device is an emulator
    205         Matcher m = sEmulatorRegexp.matcher(d.getSerialNumber());
    206         if (m.matches()) {
    207             // get the port number. This is the console port.
    208             int port;
    209             try {
    210                 port = Integer.parseInt(m.group(1));
    211                 if (port <= 0) {
    212                     return null;
    213                 }
    214             } catch (NumberFormatException e) {
    215                 // looks like we failed to get the port number. This is a bit strange since
    216                 // it's coming from a regexp that only accept digit, but we handle the case
    217                 // and return null.
    218                 return null;
    219             }
    220 
    221             EmulatorConsole console = sEmulators.get(port);
    222 
    223             if (console != null) {
    224                 // if the console exist, we ping the emulator to check the connection.
    225                 if (console.ping() == false) {
    226                     RemoveConsole(console.mPort);
    227                     console = null;
    228                 }
    229             }
    230 
    231             if (console == null) {
    232                 // no console object exists for this port so we create one, and start
    233                 // the connection.
    234                 console = new EmulatorConsole(port);
    235                 if (console.start()) {
    236                     sEmulators.put(port, console);
    237                 } else {
    238                     console = null;
    239                 }
    240             }
    241 
    242             return console;
    243         }
    244 
    245         return null;
    246     }
    247 
    248     /**
    249      * Removes the console object associated with a port from the map.
    250      * @param port The port of the console to remove.
    251      */
    252     private static synchronized void RemoveConsole(int port) {
    253         sEmulators.remove(port);
    254     }
    255 
    256     private EmulatorConsole(int port) {
    257         super();
    258         mPort = port;
    259     }
    260 
    261     /**
    262      * Starts the connection of the console.
    263      * @return true if success.
    264      */
    265     private boolean start() {
    266 
    267         InetSocketAddress socketAddr;
    268         try {
    269             InetAddress hostAddr = InetAddress.getByName(HOST);
    270             socketAddr = new InetSocketAddress(hostAddr, mPort);
    271         } catch (UnknownHostException e) {
    272             return false;
    273         }
    274 
    275         try {
    276             mSocketChannel = SocketChannel.open(socketAddr);
    277         } catch (IOException e1) {
    278             return false;
    279         }
    280 
    281         // read some stuff from it
    282         readLines();
    283 
    284         return true;
    285     }
    286 
    287     /**
    288      * Ping the emulator to check if the connection is still alive.
    289      * @return true if the connection is alive.
    290      */
    291     private synchronized boolean ping() {
    292         // it looks like we can send stuff, even when the emulator quit, but we can't read
    293         // from the socket. So we check the return of readLines()
    294         if (sendCommand(COMMAND_PING)) {
    295             return readLines() != null;
    296         }
    297 
    298         return false;
    299     }
    300 
    301     /**
    302      * Sends a KILL command to the emulator.
    303      */
    304     public synchronized void kill() {
    305         if (sendCommand(COMMAND_KILL)) {
    306             RemoveConsole(mPort);
    307         }
    308     }
    309 
    310     public synchronized String getAvdName() {
    311         if (sendCommand(COMMAND_AVD_NAME)) {
    312             String[] result = readLines();
    313             if (result != null && result.length == 2) { // this should be the name on first line,
    314                                                         // and ok on 2nd line
    315                 return result[0];
    316             } else {
    317                 // try to see if there's a message after KO
    318                 Matcher m = RE_KO.matcher(result[result.length-1]);
    319                 if (m.matches()) {
    320                     return m.group(1);
    321                 }
    322             }
    323         }
    324 
    325         return null;
    326     }
    327 
    328     /**
    329      * Get the network status of the emulator.
    330      * @return a {@link NetworkStatus} object containing the {@link GsmStatus}, or
    331      * <code>null</code> if the query failed.
    332      */
    333     public synchronized NetworkStatus getNetworkStatus() {
    334         if (sendCommand(COMMAND_NETWORK_STATUS)) {
    335             /* Result is in the format
    336                 Current network status:
    337                 download speed:      14400 bits/s (1.8 KB/s)
    338                 upload speed:        14400 bits/s (1.8 KB/s)
    339                 minimum latency:  0 ms
    340                 maximum latency:  0 ms
    341              */
    342             String[] result = readLines();
    343 
    344             if (isValid(result)) {
    345                 // we only compare agains the min latency and the download speed
    346                 // let's not rely on the order of the output, and simply loop through
    347                 // the line testing the regexp.
    348                 NetworkStatus status = new NetworkStatus();
    349                 for (String line : result) {
    350                     Matcher m = sDownloadSpeedRegexp.matcher(line);
    351                     if (m.matches()) {
    352                         // get the string value
    353                         String value = m.group(1);
    354 
    355                         // get the index from the list
    356                         status.speed = getSpeedIndex(value);
    357 
    358                         // move on to next line.
    359                         continue;
    360                     }
    361 
    362                     m = sMinLatencyRegexp.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.latency = getLatencyIndex(value);
    369 
    370                         // move on to next line.
    371                         continue;
    372                     }
    373                 }
    374 
    375                 return status;
    376             }
    377         }
    378 
    379         return null;
    380     }
    381 
    382     /**
    383      * Returns the current gsm status of the emulator
    384      * @return a {@link GsmStatus} object containing the gms status, or <code>null</code>
    385      * if the query failed.
    386      */
    387     public synchronized GsmStatus getGsmStatus() {
    388         if (sendCommand(COMMAND_GSM_STATUS)) {
    389             /*
    390              * result is in the format:
    391              * gsm status
    392              * gsm voice state: home
    393              * gsm data state:  home
    394              */
    395 
    396             String[] result = readLines();
    397             if (isValid(result)) {
    398 
    399                 GsmStatus status = new GsmStatus();
    400 
    401                 // let's not rely on the order of the output, and simply loop through
    402                 // the line testing the regexp.
    403                 for (String line : result) {
    404                     Matcher m = sVoiceStatusRegexp.matcher(line);
    405                     if (m.matches()) {
    406                         // get the string value
    407                         String value = m.group(1);
    408 
    409                         // get the index from the list
    410                         status.voice = GsmMode.getEnum(value.toLowerCase());
    411 
    412                         // move on to next line.
    413                         continue;
    414                     }
    415 
    416                     m = sDataStatusRegexp.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.data = GsmMode.getEnum(value.toLowerCase());
    423 
    424                         // move on to next line.
    425                         continue;
    426                     }
    427                 }
    428 
    429                 return status;
    430             }
    431         }
    432 
    433         return null;
    434     }
    435 
    436     /**
    437      * Sets the GSM voice mode.
    438      * @param mode the {@link GsmMode} value.
    439      * @return RESULT_OK if success, an error String otherwise.
    440      * @throws InvalidParameterException if mode is an invalid value.
    441      */
    442     public synchronized String setGsmVoiceMode(GsmMode mode) throws InvalidParameterException {
    443         if (mode == GsmMode.UNKNOWN) {
    444             throw new InvalidParameterException();
    445         }
    446 
    447         String command = String.format(COMMAND_GSM_VOICE, mode.getTag());
    448         return processCommand(command);
    449     }
    450 
    451     /**
    452      * Sets the GSM data mode.
    453      * @param mode the {@link GsmMode} value
    454      * @return {@link #RESULT_OK} if success, an error String otherwise.
    455      * @throws InvalidParameterException if mode is an invalid value.
    456      */
    457     public synchronized String setGsmDataMode(GsmMode mode) throws InvalidParameterException {
    458         if (mode == GsmMode.UNKNOWN) {
    459             throw new InvalidParameterException();
    460         }
    461 
    462         String command = String.format(COMMAND_GSM_DATA, mode.getTag());
    463         return processCommand(command);
    464     }
    465 
    466     /**
    467      * Initiate an incoming call on the emulator.
    468      * @param number a string representing the calling number.
    469      * @return {@link #RESULT_OK} if success, an error String otherwise.
    470      */
    471     public synchronized String call(String number) {
    472         String command = String.format(COMMAND_GSM_CALL, number);
    473         return processCommand(command);
    474     }
    475 
    476     /**
    477      * Cancels a current call.
    478      * @param number the number of the call to cancel
    479      * @return {@link #RESULT_OK} if success, an error String otherwise.
    480      */
    481     public synchronized String cancelCall(String number) {
    482         String command = String.format(COMMAND_GSM_CANCEL_CALL, number);
    483         return processCommand(command);
    484     }
    485 
    486     /**
    487      * Sends an SMS to the emulator
    488      * @param number The sender phone number
    489      * @param message The SMS message. \ characters must be escaped. The carriage return is
    490      * the 2 character sequence  {'\', 'n' }
    491      *
    492      * @return {@link #RESULT_OK} if success, an error String otherwise.
    493      */
    494     public synchronized String sendSms(String number, String message) {
    495         String command = String.format(COMMAND_SMS_SEND, number, message);
    496         return processCommand(command);
    497     }
    498 
    499     /**
    500      * Sets the network speed.
    501      * @param selectionIndex The index in the {@link #NETWORK_SPEEDS} table.
    502      * @return {@link #RESULT_OK} if success, an error String otherwise.
    503      */
    504     public synchronized String setNetworkSpeed(int selectionIndex) {
    505         String command = String.format(COMMAND_NETWORK_SPEED, NETWORK_SPEEDS[selectionIndex]);
    506         return processCommand(command);
    507     }
    508 
    509     /**
    510      * Sets the network latency.
    511      * @param selectionIndex The index in the {@link #NETWORK_LATENCIES} table.
    512      * @return {@link #RESULT_OK} if success, an error String otherwise.
    513      */
    514     public synchronized String setNetworkLatency(int selectionIndex) {
    515         String command = String.format(COMMAND_NETWORK_LATENCY, NETWORK_LATENCIES[selectionIndex]);
    516         return processCommand(command);
    517     }
    518 
    519     public synchronized String sendLocation(double longitude, double latitude, double elevation) {
    520 
    521         // need to make sure the string format uses dot and not comma
    522         Formatter formatter = new Formatter(Locale.US);
    523         formatter.format(COMMAND_GPS, longitude, latitude, elevation);
    524 
    525         return processCommand(formatter.toString());
    526     }
    527 
    528     /**
    529      * Sends a command to the emulator console.
    530      * @param command The command string. <b>MUST BE TERMINATED BY \n</b>.
    531      * @return true if success
    532      */
    533     private boolean sendCommand(String command) {
    534         boolean result = false;
    535         try {
    536             byte[] bCommand;
    537             try {
    538                 bCommand = command.getBytes(DEFAULT_ENCODING);
    539             } catch (UnsupportedEncodingException e) {
    540                 // wrong encoding...
    541                 return result;
    542             }
    543 
    544             // write the command
    545             AdbHelper.write(mSocketChannel, bCommand, bCommand.length, DdmPreferences.getTimeOut());
    546 
    547             result = true;
    548         } catch (Exception e) {
    549             return false;
    550         } finally {
    551             if (result == false) {
    552                 // FIXME connection failed somehow, we need to disconnect the console.
    553                 RemoveConsole(mPort);
    554             }
    555         }
    556 
    557         return result;
    558     }
    559 
    560     /**
    561      * Sends a command to the emulator and parses its answer.
    562      * @param command the command to send.
    563      * @return {@link #RESULT_OK} if success, an error message otherwise.
    564      */
    565     private String processCommand(String command) {
    566         if (sendCommand(command)) {
    567             String[] result = readLines();
    568 
    569             if (result != null && result.length > 0) {
    570                 Matcher m = RE_KO.matcher(result[result.length-1]);
    571                 if (m.matches()) {
    572                     return m.group(1);
    573                 }
    574                 return RESULT_OK;
    575             }
    576 
    577             return "Unable to communicate with the emulator";
    578         }
    579 
    580         return "Unable to send command to the emulator";
    581     }
    582 
    583     /**
    584      * Reads line from the console socket. This call is blocking until we read the lines:
    585      * <ul>
    586      * <li>OK\r\n</li>
    587      * <li>KO<msg>\r\n</li>
    588      * </ul>
    589      * @return the array of strings read from the emulator.
    590      */
    591     private String[] readLines() {
    592         try {
    593             ByteBuffer buf = ByteBuffer.wrap(mBuffer, 0, mBuffer.length);
    594             int numWaits = 0;
    595             boolean stop = false;
    596 
    597             while (buf.position() != buf.limit() && stop == false) {
    598                 int count;
    599 
    600                 count = mSocketChannel.read(buf);
    601                 if (count < 0) {
    602                     return null;
    603                 } else if (count == 0) {
    604                     if (numWaits * WAIT_TIME > STD_TIMEOUT) {
    605                         return null;
    606                     }
    607                     // non-blocking spin
    608                     try {
    609                         Thread.sleep(WAIT_TIME);
    610                     } catch (InterruptedException ie) {
    611                     }
    612                     numWaits++;
    613                 } else {
    614                     numWaits = 0;
    615                 }
    616 
    617                 // check the last few char aren't OK. For a valid message to test
    618                 // we need at least 4 bytes (OK/KO + \r\n)
    619                 if (buf.position() >= 4) {
    620                     int pos = buf.position();
    621                     if (endsWithOK(pos) || lastLineIsKO(pos)) {
    622                         stop = true;
    623                     }
    624                 }
    625             }
    626 
    627             String msg = new String(mBuffer, 0, buf.position(), DEFAULT_ENCODING);
    628             return msg.split("\r\n"); //$NON-NLS-1$
    629         } catch (IOException e) {
    630             return null;
    631         }
    632     }
    633 
    634     /**
    635      * Returns true if the 4 characters *before* the current position are "OK\r\n"
    636      * @param currentPosition The current position
    637      */
    638     private boolean endsWithOK(int currentPosition) {
    639         if (mBuffer[currentPosition-1] == '\n' &&
    640                 mBuffer[currentPosition-2] == '\r' &&
    641                 mBuffer[currentPosition-3] == 'K' &&
    642                 mBuffer[currentPosition-4] == 'O') {
    643             return true;
    644         }
    645 
    646         return false;
    647     }
    648 
    649     /**
    650      * Returns true if the last line starts with KO and is also terminated by \r\n
    651      * @param currentPosition the current position
    652      */
    653     private boolean lastLineIsKO(int currentPosition) {
    654         // first check that the last 2 characters are CRLF
    655         if (mBuffer[currentPosition-1] != '\n' ||
    656                 mBuffer[currentPosition-2] != '\r') {
    657             return false;
    658         }
    659 
    660         // now loop backward looking for the previous CRLF, or the beginning of the buffer
    661         int i = 0;
    662         for (i = currentPosition-3 ; i >= 0; i--) {
    663             if (mBuffer[i] == '\n') {
    664                 // found \n!
    665                 if (i > 0 && mBuffer[i-1] == '\r') {
    666                     // found \r!
    667                     break;
    668                 }
    669             }
    670         }
    671 
    672         // here it is either -1 if we reached the start of the buffer without finding
    673         // a CRLF, or the position of \n. So in both case we look at the characters at i+1 and i+2
    674         if (mBuffer[i+1] == 'K' && mBuffer[i+2] == 'O') {
    675             // found error!
    676             return true;
    677         }
    678 
    679         return false;
    680     }
    681 
    682     /**
    683      * Returns true if the last line of the result does not start with KO
    684      */
    685     private boolean isValid(String[] result) {
    686         if (result != null && result.length > 0) {
    687             return !(RE_KO.matcher(result[result.length-1]).matches());
    688         }
    689         return false;
    690     }
    691 
    692     private int getLatencyIndex(String value) {
    693         try {
    694             // get the int value
    695             int latency = Integer.parseInt(value);
    696 
    697             // check for the speed from the index
    698             for (int i = 0 ; i < MIN_LATENCIES.length; i++) {
    699                 if (MIN_LATENCIES[i] == latency) {
    700                     return i;
    701                 }
    702             }
    703         } catch (NumberFormatException e) {
    704             // Do nothing, we'll just return -1.
    705         }
    706 
    707         return -1;
    708     }
    709 
    710     private int getSpeedIndex(String value) {
    711         try {
    712             // get the int value
    713             int speed = Integer.parseInt(value);
    714 
    715             // check for the speed from the index
    716             for (int i = 0 ; i < DOWNLOAD_SPEEDS.length; i++) {
    717                 if (DOWNLOAD_SPEEDS[i] == speed) {
    718                     return i;
    719                 }
    720             }
    721         } catch (NumberFormatException e) {
    722             // Do nothing, we'll just return -1.
    723         }
    724 
    725         return -1;
    726     }
    727 }
    728