All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.simpligility.maven.plugins.android.standalonemojos.RunMojo Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2011 Lorenzo Villani
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.simpligility.maven.plugins.android.standalonemojos;

import com.android.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.CollectingOutputReceiver;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.ShellCommandUnresponsiveException;
import com.android.ddmlib.TimeoutException;
import com.simpligility.maven.plugins.android.AbstractAndroidMojo;
import com.simpligility.maven.plugins.android.DeviceCallback;
import com.simpligility.maven.plugins.android.common.DeviceHelper;
import com.simpligility.maven.plugins.android.config.ConfigHandler;
import com.simpligility.maven.plugins.android.config.ConfigPojo;
import com.simpligility.maven.plugins.android.config.PullParameter;
import com.simpligility.maven.plugins.android.configuration.Run;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import java.io.IOException;

import static com.simpligility.maven.plugins.android.common.AndroidExtension.APK;

import java.io.BufferedReader;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;

/**
 * Runs the first Activity shown in the top-level launcher as determined by its Intent filters.
 * 

* Android provides a component-based architecture, which means that there is no "main" function which serves as an * entry point to the APK. There's an homogeneous collection of Activity(es), Service(s), Receiver(s), etc. *

*

* The Android top-level launcher (whose purpose is to allow users to launch other applications) uses the Intent * resolution mechanism to determine which Activity(es) to show to the end user. Such activities are identified by at * least: *

    *
  • Action type: android.intent.action.MAIN
  • *
  • Category: android.intent.category.LAUNCHER
  • *
*

*

And are declared in AndroidManifest.xml as such:

*
 * <activity android:name=".ExampleActivity">
 *     <intent-filter>
 *         <action android:name="android.intent.action.MAIN" />
 *         <category android:name="android.intent.category.LAUNCHER" />
 *     </intent-filter>
 * </activity>
 * 
*

* This {@link Mojo} will try to to launch the first activity of this kind found in AndroidManifest.xml. In * case multiple activities satisfy the requirements listed above only the first declared one is run. In case there are * no "Launcher activities" declared in the manifest or no activities declared at all, this goal aborts throwing an * error. *

*

* The device parameter is taken into consideration so potentially the Activity found is started on all attached * devices. The application will NOT be deployed and running will silently fail if the application is not deployed. *

* * @author Lorenzo Villani - [email protected] * @author Manfred Moser - [email protected] * @see "http://developer.android.com/guide/topics/fundamentals.html" * @see "http://developer.android.com/guide/topics/intents/intents-filters.html" */ @Mojo( name = "run" ) public class RunMojo extends AbstractAndroidMojo { /** *

The configuration for the run goal can be set up in the plugin configuration in the pom file as:

*
     * <run>
     *     <debug>true|false|portnumber</debug>
     * </run>
     * 
*

The <debug> parameter is optional and defaults to false. Numeric values like 5432 are * parsed as port number. *

The debug parameter can also be configured as property in the pom or settings file *

     * <properties>
     *     <android.run.debug>true</android.run.debug>
     * </properties>
     * 
* or from command-line with parameter -Dandroid.run.debug=true.

*/ @Parameter @ConfigPojo private Run run; /** * Debug parameter for the the run goal. If true, the device or emulator will pause execution of the process at * startup to wait for a debugger to connect. Also see the "run" parameter documentation. Default value is false. * If the value is numeric, it is treated as a port number to forward the JDWP protocol * of the launched process to. */ @Parameter( property = "android.run.debug" ) protected String runDebug; /* the value for the debug flag after parsing pom and parameter */ @PullParameter( defaultValue = "false" ) private String parsedDebug; /** * Holds information about the "Launcher" activity. * * @author Lorenzo Villani */ private static class LauncherInfo { private String packageName; private String activity; public String getPackageName() { return packageName; } public void setPackageName( String packageName ) { this.packageName = packageName; } public String getActivity() { return activity; } public void setActivity( String activity ) { this.activity = activity; } } // ---------------------------------------------------------------------- // Public methods // ---------------------------------------------------------------------- /** * {@inheritDoc} */ @Override public void execute() throws MojoExecutionException, MojoFailureException { if ( project.getPackaging().equals( APK ) ) { try { LauncherInfo launcherInfo; launcherInfo = getLauncherActivity(); ConfigHandler configHandler = new ConfigHandler( this, this.session, this.execution ); configHandler.parseConfiguration(); launch( launcherInfo ); } catch ( Exception ex ) { getLog().info( "Unable to run launcher Activity" ); getLog().debug( ex ); } } else { getLog().info( "Project packaging is not apk, skipping run action." ); } } // ---------------------------------------------------------------------- // Private methods // ---------------------------------------------------------------------- /** * Gets the first "Launcher" Activity by running an XPath query on AndroidManifest.xml. * * @return A {@link LauncherInfo} * @throws MojoFailureException * @throws ParserConfigurationException * @throws IOException * @throws SAXException * @throws XPathExpressionException */ private LauncherInfo getLauncherActivity() throws ParserConfigurationException, SAXException, IOException, XPathExpressionException, MojoFailureException { Document document; DocumentBuilder documentBuilder; DocumentBuilderFactory documentBuilderFactory; Object result; XPath xPath; XPathExpression xPathExpression; XPathFactory xPathFactory; // // Setup JAXP stuff // documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilder = documentBuilderFactory.newDocumentBuilder(); document = documentBuilder.parse( destinationManifestFile ); xPathFactory = XPathFactory.newInstance(); xPath = xPathFactory.newXPath(); xPathExpression = xPath.compile( "//manifest/application/activity/intent-filter[action[@name=\"android.intent.action.MAIN\"] " + "and category[@name=\"android.intent.category.LAUNCHER\"]]/.." ); // // Run XPath query // result = xPathExpression.evaluate( document, XPathConstants.NODESET ); if ( result instanceof NodeList ) { NodeList activities; activities = ( NodeList ) result; if ( activities.getLength() > 0 ) { // Grab the first declared Activity LauncherInfo launcherInfo; launcherInfo = new LauncherInfo(); String activityName = activities.item( 0 ).getAttributes().getNamedItem( "android:name" ) .getNodeValue(); if ( ! activityName.contains( "." ) ) { activityName = "." + activityName; } if ( activityName.startsWith( "." ) ) { String packageName = document.getElementsByTagName( "manifest" ).item( 0 ).getAttributes() .getNamedItem( "package" ).getNodeValue(); activityName = packageName + activityName; } launcherInfo.activity = activityName; launcherInfo.packageName = renameManifestPackage != null ? renameManifestPackage : document.getDocumentElement().getAttribute( "package" ).toString(); return launcherInfo; } else { // If we get here, we couldn't find a launcher activity. throw new MojoFailureException( "Could not find a launcher activity in manifest" ); } } else { // If we get here we couldn't find any Activity throw new MojoFailureException( "Could not find any activity in manifest" ); } } /** * Executes the "Launcher activity". * * @param info A {@link LauncherInfo}. * @throws MojoFailureException * @throws MojoExecutionException */ private void launch( final LauncherInfo info ) throws MojoExecutionException, MojoFailureException { final String command; final int debugPort = findDebugPort(); command = String.format( "am start %s-n %s/%s", debugPort >= 0 ? "-D " : "", info.packageName, info.activity ); doWithDevices( new DeviceCallback() { @Override public void doWithDevice( IDevice device ) throws MojoExecutionException, MojoFailureException { String deviceLogLinePrefix = DeviceHelper.getDeviceLogLinePrefix( device ); try { getLog().info( deviceLogLinePrefix + "Attempting to start " + info.packageName + "/" + info.activity ); CollectingOutputReceiver shellOutput = new CollectingOutputReceiver(); device.executeShellCommand( command, shellOutput ); if ( shellOutput.getOutput().contains( "Error" ) ) { throw new MojoFailureException( shellOutput.getOutput() ); } if ( debugPort > 0 ) { int pid = findPid( device, "ps" ); if ( pid == -1 ) { pid = findPid( device, "ps -Af" ); } if ( pid == -1 ) { throw new MojoFailureException( "Cannot find stated process " + info.packageName ); } getLog().info( deviceLogLinePrefix + "Process " + debugPort + " launched" ); try { createForward( device, debugPort, pid ); getLog().info( deviceLogLinePrefix + "Debugger listening on " + debugPort ); } catch ( Exception ex ) { throw new MojoFailureException( "Cannot create forward tcp: " + debugPort + " jdwp: " + pid, ex ); } } } catch ( IOException ex ) { throw new MojoFailureException( deviceLogLinePrefix + "Input/Output error", ex ); } catch ( TimeoutException ex ) { throw new MojoFailureException( deviceLogLinePrefix + "Command timeout", ex ); } catch ( AdbCommandRejectedException ex ) { throw new MojoFailureException( deviceLogLinePrefix + "ADB rejected the command", ex ); } catch ( ShellCommandUnresponsiveException ex ) { throw new MojoFailureException( deviceLogLinePrefix + "Unresponsive command", ex ); } } private int findPid( IDevice device, final String cmd ) throws IOException, TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException { CollectingOutputReceiver processOutput = new CollectingOutputReceiver(); device.executeShellCommand( cmd, processOutput ); BufferedReader r = new BufferedReader( new StringReader( processOutput.getOutput() ) ); int pid = -1; for ( ;; ) { String line = r.readLine(); if ( line == null ) { break; } if ( line.endsWith( info.packageName ) ) { String[] values = line.split( " +" ); if ( values.length > 2 ) { pid = Integer.valueOf( values[1] ); break; } } } r.close(); return pid; } } ); } private static void createForward( IDevice device, int debugPort, int pid ) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Method m = Class.forName( "com.android.ddmlib.AdbHelper" ). getDeclaredMethod( "createForward", InetSocketAddress.class, device.getClass(), String.class, String.class ); m.setAccessible( true ); m.invoke( null, AndroidDebugBridge.getSocketAddress(), device, String.format( "tcp:%d", debugPort ), String.format( "jdwp:%d", pid ) ); } private int findDebugPort() { int debugPort; if ( "true".equals( parsedDebug ) ) { debugPort = 0; } else { try { debugPort = Integer.parseInt( parsedDebug ); } catch ( NumberFormatException ex ) { debugPort = -1; } } return debugPort; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy