View Javadoc
1   /*
2    * Copyright (C) 2009, 2010 Jayway AB
3    * Copyright (C) 2007-2008 JVending Masa
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package com.simpligility.maven.plugins.android;
18  
19  import com.android.ddmlib.AdbCommandRejectedException;
20  import com.android.ddmlib.AndroidDebugBridge;
21  import com.android.ddmlib.IDevice;
22  import com.android.ddmlib.ShellCommandUnresponsiveException;
23  import com.android.ddmlib.TimeoutException;
24  import com.simpligility.maven.plugins.android.common.DeviceHelper;
25  import com.simpligility.maven.plugins.android.configuration.Emulator;
26  
27  import org.apache.commons.lang3.StringUtils;
28  import org.apache.maven.plugin.MojoExecutionException;
29  import org.apache.maven.plugins.annotations.Parameter;
30  
31  import java.io.BufferedReader;
32  import java.io.File;
33  import java.io.FileWriter;
34  import java.io.IOException;
35  import java.io.InputStreamReader;
36  import java.io.PrintWriter;
37  import java.net.Socket;
38  import java.util.Arrays;
39  import java.util.List;
40  import java.util.Locale;
41  import java.util.concurrent.Callable;
42  import java.util.concurrent.ExecutorService;
43  import java.util.concurrent.Executors;
44  import java.util.concurrent.Future;
45  
46  
47  /**
48   * AbstractEmulatorMojo contains all code related to the interaction with the Android emulator. At this stage that is
49   * starting and stopping the emulator.
50   *
51   * @author Manfred Moser - manfred@simpligility.com
52   * @author Bryan O'Neil - bryan.oneil@hotmail.com
53   * @see com.simpligility.maven.plugins.android.configuration.Emulator
54   * @see com.simpligility.maven.plugins.android.standalonemojos.EmulatorStartMojo
55   * @see com.simpligility.maven.plugins.android.standalonemojos.EmulatorStopMojo
56   * @see com.simpligility.maven.plugins.android.standalonemojos.EmulatorStopAllMojo
57   */
58  public abstract class AbstractEmulatorMojo extends AbstractAndroidMojo
59  {
60      /**
61       * operating system name.
62       */
63      public static final String OS_NAME = System.getProperty( "os.name" ).toLowerCase( Locale.US );
64      private static final int MILLIS_TO_SLEEP_BETWEEN_DEVICE_ONLINE_CHECKS = 200;
65  
66      /**
67       * Even if the device finished booting, there are usually still some things going on in the background,
68       * polling at a higher frequency (un-cached!) is most probably useless
69       */
70      private static final int MILLIS_TO_SLEEP_BETWEEN_SYS_BOOTED_CHECKS = 5000;
71  
72      /**
73       * Names of device properties related to the boot state
74       */
75      private static final String[] BOOT_INDICATOR_PROP_NAMES
76              = { "dev.bootcomplete", "sys.boot_completed", "init.svc.bootanim" };
77  
78      /**
79       * Target values for properties listed in {@link #BOOT_INDICATOR_PROP_NAMES}, which indicate 'boot completed'
80       */
81      private static final String[] BOOT_INDICATOR_PROP_TARGET_VALUES = { "1", "1", "stopped" };
82  
83      /**
84       * Determines, which of the properties listed in {@link #BOOT_INDICATOR_PROP_NAMES} are required
85       * to reach the target value in {@link #BOOT_INDICATOR_PROP_TARGET_VALUES} in order to stop polling.
86       * Since one cannot be picky about what is used as a 'booted' indicator, any 'signalled' property will
87       * be used as an indicator in case of a timeout.
88       */
89      private static final boolean[] BOOT_INDICATOR_PROP_WAIT_FOR = { false, false, true };
90  
91      /**
92       * Warning threshold for narrow timeout values
93       * TODO Improve; e.g. with an additional percentage threshold
94       */
95      private static final long START_TIMEOUT_REMAINING_TIME_WARNING_THRESHOLD = 5000; //[ms]
96  
97      /**
98       * Configuration for the emulator goals. Either use the plugin configuration like this
99       * <pre>
100      * &lt;emulator&gt;
101      *   &lt;avd&gt;Default&lt;/avd&gt;
102      *   &lt;wait&gt;20000&lt;/wait&gt;
103      *   &lt;options&gt;-no-skin&lt;/options&gt;
104      *   &lt;executable&gt;emulator-arm&lt;/executable&gt;
105      *   &lt;location&gt;C:/SDK/emulator&lt;/location&gt;
106      * &lt;/emulator&gt;
107      * </pre>
108      * or configure as properties  on the command line as android.emulator.avd, android.emulator.wait,
109      * android.emulator.options and android.emulator.executable or in pom or settings file as emulator.avd,
110      * emulator.wait and emulator.options.
111      */
112     @Parameter
113     private Emulator emulator;
114 
115     /**
116      * Name of the Android Virtual Device (emulatorAvd) that will be started by the emulator. Default value is "Default"
117      *
118      * @see com.simpligility.maven.plugins.android.configuration.Emulator#avd
119      */
120     @Parameter( property = "android.emulator.avd" )
121     private String emulatorAvd;
122 
123     /**
124      * Unlock the emulator after it is started.
125      */
126     @Parameter( property = "android.emulatorUnlock", defaultValue = "false" )
127     private boolean emulatorUnlock;
128 
129     /**
130      * Wait time for the emulator start up.
131      *
132      * @see com.simpligility.maven.plugins.android.configuration.Emulator#wait
133      */
134     @Parameter( property = "android.emulator.wait" )
135     private String emulatorWait;
136 
137     /**
138      * Additional command line options for the emulator start up. This option can be used to pass any additional
139      * options desired to the invocation of the emulator. Use emulator -help for more details. An example would be
140      * "-no-skin".
141      *
142      * @see com.simpligility.maven.plugins.android.configuration.Emulator#options
143      */
144     @Parameter( property = "android.emulator.options" )
145     private String emulatorOptions;
146 
147     /**
148      * Override default emulator executable. Default uses just "emulator".
149      *
150      * @see com.simpligility.maven.plugins.android.configuration.Emulator#executable
151      */
152     @Parameter( property = "android.emulator.executable" )
153     private String emulatorExecutable;
154 
155     /**
156      * Override default path to emulator folder.
157      */
158     @Parameter( property = "android.emulator.location" )
159     private String emulatorLocation;
160 
161     /**
162      * parsed value for avd that will be used for the invocation.
163      */
164     private String parsedAvd;
165 
166     /**
167      * parsed value for options that will be used for the invocation.
168      */
169     private String parsedOptions;
170 
171     /**
172      * parsed value for wait that will be used for the invocation.
173      */
174     private String parsedWait;
175 
176     private String parsedExecutable;
177 
178     /**
179      * parsed value for location that will be used for the invocation.
180      */
181     private String parsedEmulatorLocation;
182 
183     private static final String START_EMULATOR_MSG = "Starting android emulator with script: ";
184     private static final String START_EMULATOR_WAIT_MSG = "Waiting for emulator start:";
185 
186     /**
187      * Folder that contains the startup script and the pid file.
188      */
189     private static final String SCRIPT_FOLDER = System.getProperty( "java.io.tmpdir" );
190 
191     /**
192      * Are we running on a flavour of Windows.
193      *
194      * @return
195      */
196     private boolean isWindows()
197     {
198         boolean result;
199         if ( OS_NAME.toLowerCase().contains( "windows" ) )
200         {
201             result = true;
202         }
203         else
204         {
205             result = false;
206         }
207         getLog().debug( "isWindows: " + result );
208         return result;
209     }
210 
211     /**
212      * Start the Android Emulator with the specified options.
213      *
214      * @throws org.apache.maven.plugin.MojoExecutionException
215      *
216      * @see #emulatorAvd
217      * @see #emulatorWait
218      * @see #emulatorOptions
219      */
220     protected void startAndroidEmulator() throws MojoExecutionException
221     {
222         parseParameters();
223 
224         CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
225         executor.setLogger( this.getLog() );
226 
227         try
228         {
229             String filename;
230             if ( isWindows() )
231             {
232                 filename = writeEmulatorStartScriptWindows();
233             }
234             else
235             {
236                 filename = writeEmulatorStartScriptUnix();
237             }
238 
239             final AndroidDebugBridge androidDebugBridge = initAndroidDebugBridge();
240             if ( androidDebugBridge.isConnected() )
241             {
242                 waitForInitialDeviceList( androidDebugBridge );
243                 List<IDevice> devices = Arrays.asList( androidDebugBridge.getDevices() );
244                 int numberOfDevices = devices.size();
245                 getLog().info( "Found " + numberOfDevices + " devices connected with the Android Debug Bridge" );
246 
247                 IDevice existingEmulator = findExistingEmulator( devices );
248                 if ( existingEmulator == null )
249                 {
250                     getLog().info( START_EMULATOR_MSG + filename );
251                     executor.executeCommand( filename, null );
252 
253                     getLog().info( START_EMULATOR_WAIT_MSG + parsedWait );
254                     // wait for the emulator to start up
255                     boolean booted = waitUntilDeviceIsBootedOrTimeout( androidDebugBridge );
256                     if ( booted )
257                     {
258                         getLog().info( "Emulator is up and running." );
259                         unlockEmulator( androidDebugBridge );
260                     }
261                     else
262                     {
263                         throw new MojoExecutionException( "Timeout while waiting for emulator to startup." );
264                     }
265 
266                 }
267                 else
268                 {
269                     getLog().info( String.format(
270                             "Emulator already running [Serial No: '%s', AVD Name '%s']. " + "Skipping start and wait.",
271                             existingEmulator.getSerialNumber(), existingEmulator.getAvdName() ) );
272                 }
273             }
274         }
275         catch ( Exception e )
276         {
277             throw new MojoExecutionException( "", e );
278         }
279     }
280 
281     /**
282      * Unlocks the emulator.
283      * @param androidDebugBridge
284      */
285     void unlockEmulator( AndroidDebugBridge androidDebugBridge )
286     {
287         if ( emulatorUnlock )
288         {
289             IDevice myEmulator = findExistingEmulator( Arrays.asList( androidDebugBridge.getDevices() ) );
290             int devicePort = extractPortFromDevice( myEmulator );
291             if ( devicePort == -1 )
292             {
293                 getLog().info( "Unable to retrieve port to unlock emulator "
294                         + DeviceHelper.getDescriptiveName( myEmulator ) );
295             }
296             else
297             {
298                 getLog().info( "Unlocking emulator "
299                         + DeviceHelper.getDescriptiveName( myEmulator ) );
300 
301                 sendEmulatorCommand( devicePort,
302                         "event send EV_KEY:KEY_SOFT1:1" );
303                 sendEmulatorCommand( devicePort,
304                         "event send EV_KEY:KEY_SOFT1:0" );
305                 sendEmulatorCommand( devicePort,
306                         "event send EV_KEY:KEY_SOFT1:1" );
307                 sendEmulatorCommand( devicePort,
308                         "event send EV_KEY:KEY_SOFT1:0" );
309             }
310         }
311     }
312 
313     // TODO Separate timeout params?: New param 'android.emulator.bootTimeout', rename param 'android.emulator.wait' to 'android.emulator.connectTimeout'
314     // TODO Higher default timeout(s)?: Perhaps at least for emulators, since they are probably booted or even created on demand
315     boolean waitUntilDeviceIsBootedOrTimeout( AndroidDebugBridge androidDebugBridge )
316             throws MojoExecutionException
317     {
318         final long timeout = System.currentTimeMillis() + Long.parseLong( parsedWait );
319         IDevice myEmulator;
320         boolean devOnline;
321         boolean sysBootCompleted = false;
322         long remainingTime = 0;
323 
324         //If necessary, wait until the device is online or the specified timeout is reached
325         boolean waitingForConnection = false;
326         do
327         {
328             myEmulator = findExistingEmulator( Arrays.asList( androidDebugBridge.getDevices() ) );
329             devOnline = ( myEmulator != null ) && ( myEmulator.isOnline() );
330             if ( devOnline )
331             {
332                 break;
333             }
334             else
335             {
336                 myEmulator = null;
337             }
338 
339             if ( !waitingForConnection )
340             {
341                 waitingForConnection = true;
342                 getLog().info( "Waiting for the device to go online..." );
343             }
344             try
345             {
346                 Thread.sleep( MILLIS_TO_SLEEP_BETWEEN_DEVICE_ONLINE_CHECKS );
347             }
348             catch ( InterruptedException e )
349             {
350                 throw new MojoExecutionException( "Interrupted waiting for device to become ready" );
351             }
352 
353             remainingTime = timeout - System.currentTimeMillis();
354         } while ( remainingTime > 0 );
355 
356         if ( devOnline )
357         {
358             boolean waitingForBootCompleted = false;
359             final String[] bootIndicatorPropValues = new String[ BOOT_INDICATOR_PROP_NAMES.length ];
360             boolean anyTargetStateReached = false;
361             boolean requiredTargetStatesReached = false;
362 
363             // If necessary, wait until the device's system is booted or the specified timeout is reached
364             do
365             {
366                 try
367                 {
368                     // update state flags...
369                     anyTargetStateReached = false;
370                     requiredTargetStatesReached = true;
371 
372                     for ( int indicatorProp = 0; indicatorProp < BOOT_INDICATOR_PROP_NAMES.length; ++indicatorProp )
373                     {
374                         // issue an un-cached property request
375                         boolean targetStateReached =
376                                 (
377                                         bootIndicatorPropValues[indicatorProp] != null
378                                                 &&  bootIndicatorPropValues[indicatorProp]
379                                                 .equals( BOOT_INDICATOR_PROP_TARGET_VALUES[indicatorProp] )
380                                 );
381                         if ( !targetStateReached )
382                         {
383                             // (re)query
384                             bootIndicatorPropValues[indicatorProp] =
385                                     myEmulator.getPropertySync( BOOT_INDICATOR_PROP_NAMES[indicatorProp] );
386                             targetStateReached =
387                                     (
388                                             bootIndicatorPropValues[indicatorProp] != null
389                                                     &&  bootIndicatorPropValues[indicatorProp]
390                                                     .equals( BOOT_INDICATOR_PROP_TARGET_VALUES[indicatorProp] )
391                                     );
392                         }
393                         anyTargetStateReached |= targetStateReached;
394                         requiredTargetStatesReached &=
395                                 BOOT_INDICATOR_PROP_WAIT_FOR[indicatorProp] ? targetStateReached : true;
396 
397                         getLog().debug( BOOT_INDICATOR_PROP_NAMES[indicatorProp]
398                                         + " : " +  bootIndicatorPropValues[indicatorProp]
399                                         + ( targetStateReached ? " == " : " != " )
400                                         + BOOT_INDICATOR_PROP_TARGET_VALUES[indicatorProp]
401                                         + " [" + ( targetStateReached ? "OK" : "PENDING" ) + ']'
402                         );
403                     }
404                 }
405                 catch ( TimeoutException e )
406                 {
407                     // TODO Abort here? Not too problematic since timeouts are used
408                     // optimistically ignore this exception and continue...
409                 }
410                 catch ( AdbCommandRejectedException e )
411                 {
412                     // TODO Abort here? Not too problematic since timeouts are used
413                     // optimistically ignore this exception and continue...
414                 }
415                 catch ( ShellCommandUnresponsiveException e )
416                 {
417                     // TODO Abort here? Not too problematic since timeouts are used
418                     // optimistically ignore this exception and continue...
419                 }
420                 catch ( IOException e )
421                 {
422                     throw new MojoExecutionException( "IO error during status request" , e );
423                 }
424 
425                 remainingTime = timeout - System.currentTimeMillis();
426 
427                 if ( remainingTime > 0 )
428                 {
429                     // consider the boot process to be finished, if all required states have been reached
430                     sysBootCompleted = requiredTargetStatesReached;
431                 }
432                 else
433                 {
434                     // on timeout, use any indicator
435                     sysBootCompleted = anyTargetStateReached;
436                 }
437 
438                 if ( remainingTime > 0 && !sysBootCompleted )
439                 {
440                     if ( !waitingForBootCompleted )
441                     {
442                         waitingForBootCompleted = true;
443                         getLog().info( "Waiting for the device to finish booting..." );
444                     }
445 
446                     try
447                     {
448                         Thread.sleep( MILLIS_TO_SLEEP_BETWEEN_SYS_BOOTED_CHECKS );
449                     }
450                     catch ( InterruptedException e )
451                     {
452                         throw new MojoExecutionException(
453                                 "Interrupted while waiting for the device to finish booting" );
454                     }
455                 }
456             } while ( !sysBootCompleted && remainingTime > 0 );
457             if ( sysBootCompleted && remainingTime < START_TIMEOUT_REMAINING_TIME_WARNING_THRESHOLD )
458             {
459                 getLog().warn(
460                         "Boot indicators have been signalled, but remaining time was " + remainingTime + " ms" );
461             }
462         }
463         return sysBootCompleted;
464     }
465 
466     private IDevice findExistingEmulator( List<IDevice> devices )
467     {
468         IDevice existingEmulator = null;
469 
470         for ( IDevice device : devices )
471         {
472             if ( device.isEmulator() )
473             {
474                 if ( isExistingEmulator( device ) )
475                 {
476                     existingEmulator = device;
477                     break;
478                 }
479             }
480         }
481         return existingEmulator;
482     }
483 
484     /**
485      * Checks whether the given device has the same AVD name as the device which the current command
486      * is related to. <code>true</code> returned if the device AVD names are identical (independent of case)
487      * and <code>false</code> if the device AVD names are different.
488      *
489      * @param device The device to check
490      * @return Boolean results of the check
491      */
492     private boolean isExistingEmulator( IDevice device )
493     {
494         return ( ( device.getAvdName() != null ) && ( device.getAvdName().equalsIgnoreCase( parsedAvd ) ) );
495     }
496 
497     /**
498      * Writes the script to start the emulator in the background for windows based environments.
499      *
500      * @return absolute path name of start script
501      * @throws IOException
502      * @throws MojoExecutionException
503      */
504     private String writeEmulatorStartScriptWindows() throws MojoExecutionException
505     {
506 
507         String filename = SCRIPT_FOLDER + "\\android-maven-plugin-emulator-start.vbs";
508 
509         File file = new File( filename );
510         PrintWriter writer = null;
511         try
512         {
513             writer = new PrintWriter( new FileWriter( file ) );
514 
515 
516             // command needs to be assembled before unique window title since it parses settings and sets up parsedAvd
517             // and others.
518             String command = assembleStartCommandLine();
519             String uniqueWindowTitle = "AndroidMavenPlugin-AVD" + parsedAvd;
520             writer.println( "Dim oShell" );
521             writer.println( "Set oShell = WScript.CreateObject(\"WScript.shell\")" );
522             String cmdPath = System.getenv( "COMSPEC" );
523             if ( cmdPath == null )
524             {
525                 cmdPath = "cmd.exe";
526             }
527             String cmd = cmdPath + " /X /C START /SEPARATE \"\"" + uniqueWindowTitle + "\"\"  " + command.trim();
528             writer.println( "oShell.run \"" + cmd + "\"" );
529         }
530         catch ( IOException e )
531         {
532             getLog().error( "Failure writing file " + filename );
533         }
534         finally
535         {
536             if ( writer != null )
537             {
538                 writer.flush();
539                 writer.close();
540             }
541         }
542         file.setExecutable( true );
543         return filename;
544     }
545 
546     /**
547      * Writes the script to start the emulator in the background for unix based environments.
548      *
549      * @return absolute path name of start script
550      * @throws IOException
551      * @throws MojoExecutionException
552      */
553     private String writeEmulatorStartScriptUnix() throws MojoExecutionException
554     {
555         String filename = SCRIPT_FOLDER + "/android-maven-plugin-emulator-start.sh";
556 
557         File sh;
558         sh = new File( "/bin/bash" );
559         if ( !sh.exists() )
560         {
561             sh = new File( "/usr/bin/bash" );
562         }
563         if ( !sh.exists() )
564         {
565             sh = new File( "/bin/sh" );
566         }
567 
568         File file = new File( filename );
569         PrintWriter writer = null;
570         try
571         {
572             writer = new PrintWriter( new FileWriter( file ) );
573             writer.println( "#!" + sh.getAbsolutePath() );
574             writer.print( assembleStartCommandLine() );
575             writer.print( " 1>/dev/null 2>&1 &" ); // redirect outputs and run as background task
576         }
577         catch ( IOException e )
578         {
579             getLog().error( "Failure writing file " + filename );
580         }
581         finally
582         {
583             if ( writer != null )
584             {
585                 writer.flush();
586                 writer.close();
587             }
588         }
589         file.setExecutable( true );
590         return filename;
591     }
592 
593     /**
594      * Stop the running Android Emulator.
595      *
596      * @throws org.apache.maven.plugin.MojoExecutionException
597      *
598      */
599     protected void stopAndroidEmulator() throws MojoExecutionException
600     {
601         parseParameters();
602 
603         final AndroidDebugBridge androidDebugBridge = initAndroidDebugBridge();
604         if ( androidDebugBridge.isConnected() )
605         {
606             List<IDevice> devices = Arrays.asList( androidDebugBridge.getDevices() );
607             int numberOfDevices = devices.size();
608             getLog().info( "Found " + numberOfDevices + " devices connected with the Android Debug Bridge" );
609 
610             for ( IDevice device : devices )
611             {
612                 if ( device.isEmulator() )
613                 {
614                     if ( isExistingEmulator( device ) )
615                     {
616                         stopEmulator( device );
617                     }
618                 }
619                 else
620                 {
621                     getLog().info( "Skipping stop. Not an emulator. " + DeviceHelper.getDescriptiveName( device ) );
622                 }
623             }
624         }
625     }
626 
627     /**
628      * Stop the running Android Emulators.
629      *
630      * @throws org.apache.maven.plugin.MojoExecutionException
631      *
632      */
633     protected void stopAndroidEmulators() throws MojoExecutionException
634     {
635         final AndroidDebugBridge androidDebugBridge = initAndroidDebugBridge();
636         if ( androidDebugBridge.isConnected() )
637         {
638             List<IDevice> devices = Arrays.asList( androidDebugBridge.getDevices() );
639             int numberOfDevices = devices.size();
640             getLog().info( "Found " + numberOfDevices + " devices connected with the Android Debug Bridge" );
641 
642             for ( IDevice device : devices )
643             {
644                 if ( device.isEmulator() )
645                 {
646                     stopEmulator( device );
647                 }
648                 else
649                 {
650                     getLog().info( "Skipping stop. Not an emulator. " + DeviceHelper.getDescriptiveName( device ) );
651                 }
652             }
653         }
654     }
655 
656     /**
657      * This method contains the code required to stop an emulator
658      *
659      * @param device The device to stop
660      */
661     private void stopEmulator( IDevice device )
662     {
663         int devicePort = extractPortFromDevice( device );
664         if ( devicePort == -1 )
665         {
666             getLog().info( "Unable to retrieve port to stop emulator " + DeviceHelper.getDescriptiveName( device ) );
667         }
668         else
669         {
670             getLog().info( "Stopping emulator " + DeviceHelper.getDescriptiveName( device ) );
671 
672             sendEmulatorCommand( devicePort, "avd stop" );
673             boolean killed = sendEmulatorCommand( devicePort, "kill" );
674             if ( !killed )
675             {
676                 getLog().info( "Emulator failed to stop " + DeviceHelper.getDescriptiveName( device ) );
677             }
678             else
679             {
680                 getLog().info( "Emulator stopped successfully " + DeviceHelper.getDescriptiveName( device ) );
681             }
682         }
683     }
684 
685     /**
686      * This method extracts a port number from the serial number of a device.
687      * It assumes that the device name is of format [xxxx-nnnn] where nnnn is the
688      * port number.
689      *
690      * @param device The device to extract the port number from.
691      * @return Returns the port number of the device
692      */
693     private int extractPortFromDevice( IDevice device )
694     {
695         String portStr = StringUtils.substringAfterLast( device.getSerialNumber(), "-" );
696         if ( StringUtils.isNotBlank( portStr ) && StringUtils.isNumeric( portStr ) )
697         {
698             return Integer.parseInt( portStr );
699         }
700 
701         //If the port is not available then return -1
702         return -1;
703     }
704 
705     /**
706      * Sends a user command to the running emulator via its telnet interface.
707      *
708      * @param port    The emulator's telnet port.
709      * @param command The command to execute on the emulator's telnet interface.
710      * @return Whether sending the command succeeded.
711      */
712     private boolean sendEmulatorCommand(
713             //final Launcher launcher,
714             //final PrintStream logger,
715             final int port, final String command )
716     {
717         Callable<Boolean> task = new Callable<Boolean>()
718         {
719             public Boolean call() throws IOException
720             {
721                 Socket socket = null;
722                 BufferedReader in = null;
723                 PrintWriter out = null;
724                 try
725                 {
726                     socket = new Socket( "127.0.0.1", port );
727                     out = new PrintWriter( socket.getOutputStream(), true );
728                     in = new BufferedReader( new InputStreamReader( socket.getInputStream() ) );
729                     if ( in.readLine() == null )
730                     {
731                         return false;
732                     }
733 
734                     out.write( command );
735                     out.write( "\r\n" );
736                 }
737                 finally
738                 {
739                     try
740                     {
741                         out.close();
742                         in.close();
743                         socket.close();
744                     }
745                     catch ( Exception e )
746                     {
747                         // Do nothing
748                     }
749                 }
750 
751                 return true;
752             }
753 
754             private static final long serialVersionUID = 1L;
755         };
756 
757         boolean result = false;
758         try
759         {
760             ExecutorService executor = Executors.newSingleThreadExecutor();
761             Future<Boolean> future = executor.submit( task );
762             result = future.get();
763         }
764         catch ( Exception e )
765         {
766             getLog().error( String.format( "Failed to execute emulator command '%s': %s", command, e ) );
767         }
768 
769         return result;
770     }
771 
772     /**
773      * Assemble the command line for starting the emulator based on the parameters supplied in the pom file and on the
774      * command line. It should not be that painful to do work with command line and pom supplied values but evidently
775      * it is.
776      *
777      * @return
778      * @throws MojoExecutionException
779      * @see com.simpligility.maven.plugins.android.configuration.Emulator
780      */
781     private String assembleStartCommandLine() throws MojoExecutionException
782     {
783         String emulatorPath;
784         if ( !"SdkTools".equals( parsedEmulatorLocation ) )
785         {
786             emulatorPath = new File( parsedEmulatorLocation, parsedExecutable ).getAbsolutePath();
787         }
788         else
789         {
790             emulatorPath = new File( getAndroidSdk().getToolsPath(), parsedExecutable ).getAbsolutePath();
791         }
792         StringBuilder startCommandline = new StringBuilder( "\"\"" ).append( emulatorPath ).append( "\"\"" )
793                 .append( " -avd " ).append( parsedAvd ).append( " " );
794         if ( !StringUtils.isEmpty( parsedOptions ) )
795         {
796             startCommandline.append( parsedOptions );
797         }
798         getLog().info( "Android emulator command: " + startCommandline );
799         return startCommandline.toString();
800     }
801 
802     private void parseParameters()
803     {
804         // <emulator> exist in pom file
805         if ( emulator != null )
806         {
807             // <emulator><avd> exists in pom file
808             if ( emulator.getAvd() != null )
809             {
810                 parsedAvd = emulator.getAvd();
811             }
812             else
813                 {
814                 parsedAvd = determineAvd();
815             }
816             // <emulator><options> exists in pom file
817             if ( emulator.getOptions() != null )
818             {
819                 parsedOptions = emulator.getOptions();
820             }
821             else
822                 {
823                 parsedOptions = determineOptions();
824             }
825             // <emulator><wait> exists in pom file
826             if ( emulator.getWait() != null )
827             {
828                 parsedWait = emulator.getWait();
829             }
830             else
831                 {
832                 parsedWait = determineWait();
833             }
834             // <emulator><emulatorExecutable> exists in pom file
835             if ( emulator.getExecutable() != null )
836             {
837                 parsedExecutable = emulator.getExecutable();
838             }
839             else
840                 {
841                 parsedExecutable = determineExecutable();
842             }
843             // <emulator><location> exists in pom file
844             if ( emulator.getLocation() != null )
845             {
846                 parsedEmulatorLocation = emulator.getLocation();
847             }
848             else
849             {
850             parsedEmulatorLocation = determineEmulatorLocation();
851             }
852         }
853         // commandline options
854         else
855         {
856             parsedAvd = determineAvd();
857             parsedOptions = determineOptions();
858             parsedWait = determineWait();
859             parsedExecutable = determineExecutable();
860             parsedEmulatorLocation = determineEmulatorLocation();
861         }
862     }
863 
864     /**
865      * Get executable value for emulator from command line options or default to "emulator".
866      *
867      * @return
868      */
869     private String determineExecutable()
870     {
871         String emulator;
872         if ( emulatorExecutable != null )
873         {
874             emulator = emulatorExecutable;
875         }
876         else
877         {
878             emulator = "emulator";
879         }
880         return emulator;
881     }
882 
883     /**
884      * Get wait value for emulator from command line option.
885      *
886      * @return if available return command line value otherwise return default value (5000).
887      */
888     String determineWait()
889     {
890         String wait;
891         if ( emulatorWait != null )
892         {
893             wait = emulatorWait;
894         }
895         else
896         {
897             wait = "5000";
898         }
899         return wait;
900     }
901 
902     /**
903      * Get options value for emulator from command line option.
904      *
905      * @return if available return command line value otherwise return default value ("").
906      */
907     private String determineOptions()
908     {
909         String options;
910         if ( emulatorOptions != null )
911         {
912             options = emulatorOptions;
913         }
914         else
915         {
916             options = "";
917         }
918         return options;
919     }
920 
921     /**
922      * Get avd value for emulator from command line option.
923      *
924      * @return if available return command line value otherwise return default value ("Default").
925      */
926     String determineAvd()
927     {
928         String avd;
929         if ( emulatorAvd != null )
930         {
931             avd = emulatorAvd;
932         }
933         else
934         {
935             avd = "Default";
936         }
937         return avd;
938     }
939 
940     /**
941      * Get location value for emulator from command line option.
942      *
943      * @return if available return command line value otherwise return default value ("SdkTools").
944      */
945     String determineEmulatorLocation()
946     {
947         String location;
948         if ( emulatorLocation != null )
949         {
950             location = emulatorLocation;
951         }
952         else
953         {
954             location = "SdkTools";
955         }
956         return location;
957     }
958 
959 }