• 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         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      */
RemoveConsole(int port)252     private static synchronized void RemoveConsole(int port) {
253         sEmulators.remove(port);
254     }
255 
EmulatorConsole(int port)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      */
start()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      */
ping()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      */
kill()304     public synchronized void kill() {
305         if (sendCommand(COMMAND_KILL)) {
306             RemoveConsole(mPort);
307         }
308     }
309 
getAvdName()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      */
getNetworkStatus()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      */
getGsmStatus()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      */
setGsmVoiceMode(GsmMode mode)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      */
setGsmDataMode(GsmMode mode)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      */
call(String number)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      */
cancelCall(String number)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      */
sendSms(String number, String message)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      */
setNetworkSpeed(int selectionIndex)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      */
setNetworkLatency(int selectionIndex)514     public synchronized String setNetworkLatency(int selectionIndex) {
515         String command = String.format(COMMAND_NETWORK_LATENCY, NETWORK_LATENCIES[selectionIndex]);
516         return processCommand(command);
517     }
518 
sendLocation(double longitude, double latitude, double elevation)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      */
sendCommand(String command)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      */
processCommand(String command)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      */
readLines()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      */
endsWithOK(int currentPosition)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      */
lastLineIsKO(int currentPosition)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      */
isValid(String[] result)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 
getLatencyIndex(String value)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 
getSpeedIndex(String value)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