View Javadoc

1   /*
2    * Copyright (C) 2009, 2010 Jayway AB
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  package com.jayway.maven.plugins.android;
17  
18  import com.android.SdkConstants;
19  import com.android.sdklib.BuildToolInfo;
20  import com.android.sdklib.IAndroidTarget;
21  import com.android.sdklib.SdkManager;
22  import com.android.utils.NullLogger;
23  import org.apache.maven.plugin.MojoExecutionException;
24  
25  import java.io.File;
26  import java.io.FileInputStream;
27  import java.io.IOException;
28  import java.util.Properties;
29  
30  /**
31   * Represents an Android SDK.
32   * 
33   * @author hugo.josefson@jayway.com
34   * @author Manfred Moser <manfred@simpligility.com>
35   */
36  public class AndroidSdk
37  {
38  
39      /**
40       * property file in each platform folder with details about platform.
41       */
42      private static final String SOURCE_PROPERTIES_FILENAME = "source.properties";
43      /**
44       * property name for the sdk tools revision in sdk/tools/lib source.properties
45       */
46      private static final String SDK_TOOLS_REVISION_PROPERTY = "Pkg.Revision";
47  
48      /**
49       * folder name for the sdk sub folder that contains the different platform versions.
50       */
51      private static final String PLATFORMS_FOLDER_NAME = "platforms";
52  
53      private static final String PARAMETER_MESSAGE = "Please provide a proper Android SDK directory path as "
54              + "configuration parameter <sdk><path>...</path></sdk> in the plugin <configuration/>. As an alternative,"
55              + " you may add the parameter to commandline: -Dandroid.sdk.path=... or set environment variable "
56              + AbstractAndroidMojo.ENV_ANDROID_HOME + ".";
57  
58      private final File sdkPath;
59      private File platformToolsPath;
60      private File toolsPath;
61  
62      private IAndroidTarget androidTarget;
63      private SdkManager sdkManager;
64      private int sdkMajorVersion;
65  
66      public AndroidSdk( File sdkPath, String platformOrApiLevel )
67      {
68          this.sdkPath = sdkPath;
69  
70          if ( sdkPath != null )
71          {
72              sdkManager = SdkManager.createManager( sdkPath.getPath(), new NullLogger() );
73              platformToolsPath = new File( sdkPath, SdkConstants.FD_PLATFORM_TOOLS );
74              toolsPath = new File( sdkPath, SdkConstants.FD_TOOLS );
75  
76              if ( sdkManager == null )
77              {
78                  throw invalidSdkException( sdkPath, platformOrApiLevel );
79              }
80          }
81          loadSDKToolsMajorVersion();
82  
83          if ( platformOrApiLevel == null )
84          {
85              throw new InvalidConfigurationException( " No Android Platform Version/API Level has been configured. " 
86                      + " Add e.g. <sdk><platform>17</platform></sdk> to the plugin configuration." );
87          }
88          else
89          {
90              androidTarget = findPlatformByNameOrApiLevel( platformOrApiLevel );
91              if ( androidTarget == null )
92              {
93                  throw invalidSdkException( sdkPath, platformOrApiLevel );
94              }
95          }
96      }
97  
98      private InvalidSdkException invalidSdkException( File sdkPath, String platformOrApiLevel )
99      {
100         throw new InvalidSdkException( "Invalid SDK: Platform/API level " + platformOrApiLevel
101                 + " not available. This command should give you all you need:\n" + sdkPath.getAbsolutePath()
102                 + File.separator + "tools" + File.separator + "android update sdk --no-ui --obsolete --force" );
103     }
104 
105     private IAndroidTarget findPlatformByNameOrApiLevel( String platformOrApiLevel )
106     {
107         // try find by api level first
108         IAndroidTarget target = sdkManager.getTargetFromHashString(
109                 IAndroidTarget.PLATFORM_HASH_PREFIX + platformOrApiLevel );
110         if ( target != null )
111         {
112             return target;
113         }
114 
115         // fallback to searching for platform on standard Android platforms (isPlatform() is true)
116         for ( IAndroidTarget t: sdkManager.getTargets() )
117         {
118             if ( t.isPlatform() && t.getVersionName().equals( platformOrApiLevel ) )
119             {
120                 return t;
121             }
122         }
123         return null;
124     }
125 
126 
127 
128     /**
129      * The file system layout of the SDK. Should probably be removed since the 15 layout is very old and probably wont
130      * work completely. No urgency though..
131      */
132     public enum Layout
133     {
134         LAYOUT_1_5, LAYOUT_2_3;
135     }
136     public Layout getLayout()
137     {
138 
139         assertPathIsDirectory( sdkPath );
140 
141 
142         if ( platformToolsPath.exists() && platformToolsPath.isDirectory() )
143         {
144             return Layout.LAYOUT_2_3;
145         }
146 
147         final File platforms = new File( sdkPath, PLATFORMS_FOLDER_NAME );
148         if ( platforms.exists() && platforms.isDirectory() )
149         {
150             return Layout.LAYOUT_1_5;
151         }
152 
153         throw new InvalidSdkException( "Android SDK could not be identified from path \"" + sdkPath + "\". "
154                 + PARAMETER_MESSAGE );
155     }
156 
157     private void assertPathIsDirectory( final File path )
158     {
159         if ( path == null )
160         {
161             throw new InvalidSdkException( PARAMETER_MESSAGE );
162         }
163         if ( !path.isDirectory() )
164         {
165             throw new InvalidSdkException( "Path \"" + path + "\" is not a directory. " + PARAMETER_MESSAGE );
166         }
167     }
168 
169     /**
170      * Get the aapt tool path.
171      *
172      * @return
173      */
174     public String getAaptPath()
175     {
176         return getPathForBuildTool( BuildToolInfo.PathId.AAPT );
177     }
178 
179     /**
180      * Get the aild tool path
181      * @return
182      */
183     public String getAidlPath()
184     {
185         return getPathForBuildTool( BuildToolInfo.PathId.AIDL );
186     }
187 
188     /**
189      * Get the path for dx.jar
190      * @return
191      */
192     public String getDxJarPath()
193     {
194         return getPathForBuildTool( BuildToolInfo.PathId.DX_JAR );
195     }
196 
197     /**
198      * Get the android debug tool path (adb).
199      *
200      * @return
201      */
202     public String getAdbPath()
203     {
204         return getPathForPlatformTool( SdkConstants.FN_ADB );
205     }
206 
207     /**
208      * Get the android zipalign path.
209      *
210      * @return
211      */
212     public String getZipalignPath()
213     {
214         return getPathForTool( SdkConstants.FN_ZIPALIGN );
215     }
216 
217     /**
218      * Get the android lint path.
219      * 
220      * @return
221      */
222     public String getLintPath()
223     {
224         return getPathForTool( "lint" + ext( ".bat", "" ) );
225     }
226 
227     /**
228      * Get the android monkey runner path.
229      * 
230      * @return
231      */
232     public String getMonkeyRunnerPath()
233     {
234         return getPathForTool( "monkeyrunner" + ext( ".bat", "" ) );
235     }
236 
237     /**
238      * Get the apkbuilder path.
239      *
240      * @return
241      */
242     public String getApkBuilderPath()
243     {
244         return getPathForTool( "apkbuilder" + ext( ".bat", "" ) );
245     }
246 
247     /**
248      * Get the android tool path.
249      *
250      * @return
251      */
252     public String getAndroidPath()
253     {
254         return getPathForTool( SdkConstants.androidCmdName() );
255     }
256 
257     /**
258      * Get the path to the tools directory.
259      * @return
260      */
261     public File getToolsPath()
262     {
263         return toolsPath;
264     }
265 
266     private String getPathForBuildTool( BuildToolInfo.PathId pathId )
267     {
268         if ( androidTarget != null )
269         {
270             return androidTarget.getBuildToolInfo().getPath( pathId );
271         }
272         // if no valid target is defined used the latest
273         return sdkManager.getLatestBuildTool().getPath( pathId );
274     }
275 
276     private String getPathForPlatformTool( String tool )
277     {
278         return new File( platformToolsPath, tool ).getAbsolutePath();
279     }
280 
281     private String getPathForTool( String tool )
282     {
283         return new File( toolsPath, tool ).getAbsolutePath();
284     }
285 
286     private static String ext( String windowsExtension, String nonWindowsExtension )
287     {
288         if ( SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS )
289         {
290             return windowsExtension;
291         }
292         else
293         {
294             return nonWindowsExtension;
295         }
296     }
297 
298     /**
299      * Returns the complete path for <code>framework.aidl</code>, based on this SDK.
300      * 
301      * @return the complete path as a <code>String</code>, including the filename.
302      */
303     public String getPathForFrameworkAidl()
304     {
305         return androidTarget.getPath( IAndroidTarget.ANDROID_AIDL );
306     }
307 
308     /**
309      * Resolves the android.jar from this SDK.
310      * 
311      * @return a <code>File</code> pointing to the android.jar file.
312      * @throws org.apache.maven.plugin.MojoExecutionException
313      *             if the file can not be resolved.
314      */
315     public File getAndroidJar() throws MojoExecutionException
316     {
317         return new File( androidTarget.getPath( IAndroidTarget.ANDROID_JAR ) );
318     }
319 
320     /**
321      * Resolves the sdklib.jar from this SDK.
322      * 
323      * @return a <code>File</code> pointing to the sdklib.jar file.
324      * @throws org.apache.maven.plugin.MojoExecutionException
325      *             if the file can not be resolved.
326      */
327     public File getSDKLibJar() throws MojoExecutionException
328     {
329         // The file is sdkPath/tools/lib/sdklib.jar
330         File sdklib = new File( sdkPath + "/tools/lib/sdklib.jar" );
331         if ( sdklib.exists() )
332         {
333             return sdklib;
334         }
335         throw new MojoExecutionException( "Can't find the 'sdklib.jar' : " + sdklib.getAbsolutePath() );
336     }
337 
338     /**
339      * Resolves the path for this SDK.
340      * 
341      * @return a <code>File</code> pointing to the SDk Directory.
342      * @throws org.apache.maven.plugin.MojoExecutionException
343      *             if the file can not be resolved.
344      */
345     public File getSdkPath() throws MojoExecutionException
346     {
347         if ( sdkPath.exists() )
348         {
349             return sdkPath;
350         }
351         throw new MojoExecutionException( "Can't find the SDK directory : " + sdkPath.getAbsolutePath() );
352     }
353 
354     /**
355      * This method returns the previously specified version. However, if none have been specified it returns the
356      * "latest" version.
357      */
358     public File getPlatform()
359     {
360         assertPathIsDirectory( sdkPath );
361 
362         final File platformsDirectory = new File( sdkPath, PLATFORMS_FOLDER_NAME );
363         assertPathIsDirectory( platformsDirectory );
364 
365         final File platformDirectory;
366         if ( androidTarget == null )
367         {
368             IAndroidTarget latestTarget = null;
369             for ( IAndroidTarget target:  sdkManager.getTargets() )
370             {
371                 if ( target.isPlatform() )
372                 {
373                     if ( latestTarget == null
374                             || target.getVersion().getApiLevel() > latestTarget.getVersion().getApiLevel() )
375                     {
376                         latestTarget = target;
377                     }
378                 }
379             }
380             platformDirectory = new File ( latestTarget.getLocation() );
381         }
382         else
383         {
384             platformDirectory = new File( androidTarget.getLocation() );
385         }
386         assertPathIsDirectory( platformDirectory );
387         return platformDirectory;
388     }
389 
390     /**
391      * Loads the SDK Tools version
392      */
393     private void loadSDKToolsMajorVersion()
394     {
395         File propFile = new File( sdkPath, "tools/" + SOURCE_PROPERTIES_FILENAME );
396         Properties properties = new Properties();
397         try
398         {
399             properties.load( new FileInputStream( propFile ) );
400         }
401         catch ( IOException e )
402         {
403             throw new InvalidSdkException( "Error reading " + propFile.getAbsoluteFile() );
404         }
405 
406         if ( properties.containsKey( SDK_TOOLS_REVISION_PROPERTY ) )
407         {
408             try
409             {
410                 String versionString = properties.getProperty( SDK_TOOLS_REVISION_PROPERTY );
411                 String majorVersion;
412                 if ( versionString.matches( ".*[\\.| ].*" ) )
413                 {
414                     String[] versions = versionString.split( "[\\.| ]" );
415                     majorVersion = versions[ 0 ];
416                 }
417                 else
418                 {
419                     majorVersion = versionString;
420                 }
421                 sdkMajorVersion = Integer.parseInt( majorVersion );
422             }
423             catch ( NumberFormatException e )
424             {
425                 throw new InvalidSdkException( "Error - The property '" + SDK_TOOLS_REVISION_PROPERTY
426                         + "' in the SDK source.properties file  number is not an Integer: "
427                         + properties.getProperty( SDK_TOOLS_REVISION_PROPERTY ) );
428             }
429         }
430     }
431 
432     /**
433      * Returns the version of the SDK Tools.
434      * 
435      * @return
436      */
437     public int getSdkMajorVersion()
438     {
439         return sdkMajorVersion;
440     }
441 }