• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
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.ide.eclipse.adt.internal.build;
18 
19 import com.android.ide.eclipse.adt.AdtConstants;
20 import com.android.ide.eclipse.adt.AdtPlugin;
21 import com.android.ide.eclipse.adt.AndroidConstants;
22 import com.android.ide.eclipse.adt.internal.project.ApkInstallManager;
23 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
24 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
25 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
26 import com.android.ide.eclipse.adt.internal.sdk.DexWrapper;
27 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
28 import com.android.jarutils.DebugKeyProvider;
29 import com.android.jarutils.JavaResourceFilter;
30 import com.android.jarutils.SignedJarBuilder;
31 import com.android.jarutils.DebugKeyProvider.IKeyGenOutput;
32 import com.android.jarutils.DebugKeyProvider.KeytoolException;
33 import com.android.jarutils.SignedJarBuilder.IZipEntryFilter;
34 import com.android.prefs.AndroidLocation.AndroidLocationException;
35 import com.android.sdklib.IAndroidTarget;
36 import com.android.sdklib.SdkConstants;
37 import com.android.sdklib.internal.project.ApkSettings;
38 
39 import org.eclipse.core.resources.IContainer;
40 import org.eclipse.core.resources.IFile;
41 import org.eclipse.core.resources.IFolder;
42 import org.eclipse.core.resources.IMarker;
43 import org.eclipse.core.resources.IProject;
44 import org.eclipse.core.resources.IResource;
45 import org.eclipse.core.resources.IResourceDelta;
46 import org.eclipse.core.resources.IResourceDeltaVisitor;
47 import org.eclipse.core.resources.IWorkspace;
48 import org.eclipse.core.resources.IWorkspaceRoot;
49 import org.eclipse.core.resources.ResourcesPlugin;
50 import org.eclipse.core.runtime.CoreException;
51 import org.eclipse.core.runtime.IPath;
52 import org.eclipse.core.runtime.IProgressMonitor;
53 import org.eclipse.core.runtime.IStatus;
54 import org.eclipse.core.runtime.Path;
55 import org.eclipse.core.runtime.Status;
56 import org.eclipse.jdt.core.IJavaProject;
57 import org.eclipse.jdt.core.JavaCore;
58 import org.eclipse.jdt.core.JavaModelException;
59 import org.eclipse.jface.preference.IPreferenceStore;
60 
61 import java.io.File;
62 import java.io.FileInputStream;
63 import java.io.FileOutputStream;
64 import java.io.IOException;
65 import java.io.PrintStream;
66 import java.security.GeneralSecurityException;
67 import java.security.PrivateKey;
68 import java.security.cert.X509Certificate;
69 import java.text.DateFormat;
70 import java.util.ArrayList;
71 import java.util.Date;
72 import java.util.Map;
73 import java.util.Set;
74 import java.util.Map.Entry;
75 
76 public class ApkBuilder extends BaseBuilder {
77 
78     public static final String ID = "com.android.ide.eclipse.adt.ApkBuilder"; //$NON-NLS-1$
79 
80     private static final String PROPERTY_CONVERT_TO_DEX = "convertToDex"; //$NON-NLS-1$
81     private static final String PROPERTY_PACKAGE_RESOURCES = "packageResources"; //$NON-NLS-1$
82     private static final String PROPERTY_BUILD_APK = "buildApk"; //$NON-NLS-1$
83 
84     private static final String DX_PREFIX = "Dx"; //$NON-NLS-1$
85 
86     /**
87      * Dex conversion flag. This is set to true if one of the changed/added/removed
88      * file is a .class file. Upon visiting all the delta resource, if this
89      * flag is true, then we know we'll have to make the "classes.dex" file.
90      */
91     private boolean mConvertToDex = false;
92 
93     /**
94      * Package resources flag. This is set to true if one of the changed/added/removed
95      * file is a resource file. Upon visiting all the delta resource, if
96      * this flag is true, then we know we'll have to repackage the resources.
97      */
98     private boolean mPackageResources = false;
99 
100     /**
101      * Final package build flag.
102      */
103     private boolean mBuildFinalPackage = false;
104 
105     private PrintStream mOutStream = null;
106     private PrintStream mErrStream = null;
107 
108     /**
109      * Basic Resource Delta Visitor class to check if a referenced project had a change in its
110      * compiled java files.
111      */
112     private static class ReferencedProjectDeltaVisitor implements IResourceDeltaVisitor {
113 
114         private boolean mConvertToDex = false;
115         private boolean mMakeFinalPackage;
116 
117         private IPath mOutputFolder;
118         private ArrayList<IPath> mSourceFolders;
119 
ReferencedProjectDeltaVisitor(IJavaProject javaProject)120         private ReferencedProjectDeltaVisitor(IJavaProject javaProject) {
121             try {
122                 mOutputFolder = javaProject.getOutputLocation();
123                 mSourceFolders = BaseProjectHelper.getSourceClasspaths(javaProject);
124             } catch (JavaModelException e) {
125             } finally {
126             }
127         }
128 
129         /**
130          * {@inheritDoc}
131          * @throws CoreException
132          */
visit(IResourceDelta delta)133         public boolean visit(IResourceDelta delta) throws CoreException {
134             //  no need to keep looking if we already know we need to convert
135             // to dex and make the final package.
136             if (mConvertToDex && mMakeFinalPackage) {
137                 return false;
138             }
139 
140             // get the resource and the path segments.
141             IResource resource = delta.getResource();
142             IPath resourceFullPath = resource.getFullPath();
143 
144             if (mOutputFolder.isPrefixOf(resourceFullPath)) {
145                 int type = resource.getType();
146                 if (type == IResource.FILE) {
147                     String ext = resource.getFileExtension();
148                     if (AndroidConstants.EXT_CLASS.equals(ext)) {
149                         mConvertToDex = true;
150                     }
151                 }
152                 return true;
153             } else {
154                 for (IPath sourceFullPath : mSourceFolders) {
155                     if (sourceFullPath.isPrefixOf(resourceFullPath)) {
156                         int type = resource.getType();
157                         if (type == IResource.FILE) {
158                             // check if the file is a valid file that would be
159                             // included during the final packaging.
160                             if (checkFileForPackaging((IFile)resource)) {
161                                 mMakeFinalPackage = true;
162                             }
163 
164                             return false;
165                         } else if (type == IResource.FOLDER) {
166                             // if this is a folder, we check if this is a valid folder as well.
167                             // If this is a folder that needs to be ignored, we must return false,
168                             // so that we ignore its content.
169                             return checkFolderForPackaging((IFolder)resource);
170                         }
171                     }
172                 }
173             }
174 
175             return true;
176         }
177 
178         /**
179          * Returns if one of the .class file was modified.
180          */
needDexConvertion()181         boolean needDexConvertion() {
182             return mConvertToDex;
183         }
184 
needMakeFinalPackage()185         boolean needMakeFinalPackage() {
186             return mMakeFinalPackage;
187         }
188     }
189 
190     /**
191      * {@link IZipEntryFilter} to filter out everything that is not a standard java resources.
192      * <p/>Used in {@link SignedJarBuilder#writeZip(java.io.InputStream, IZipEntryFilter)} when
193      * we only want the java resources from external jars.
194      */
195     private final IZipEntryFilter mJavaResourcesFilter = new JavaResourceFilter();
196 
ApkBuilder()197     public ApkBuilder() {
198         super();
199     }
200 
201     // build() returns a list of project from which this project depends for future compilation.
202     @SuppressWarnings("unchecked")
203     @Override
build(int kind, Map args, IProgressMonitor monitor)204     protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
205             throws CoreException {
206         // get a project object
207         IProject project = getProject();
208 
209         // list of referenced projects.
210         IProject[] referencedProjects = null;
211 
212         try {
213             // Top level check to make sure the build can move forward.
214             abortOnBadSetup(project);
215 
216             // get the list of referenced projects.
217             referencedProjects = ProjectHelper.getReferencedProjects(project);
218             IJavaProject[] referencedJavaProjects = getJavaProjects(referencedProjects);
219 
220             // get the output folder, this method returns the path with a trailing
221             // separator
222             IJavaProject javaProject = JavaCore.create(project);
223             IFolder outputFolder = BaseProjectHelper.getOutputFolder(project);
224 
225             // now we need to get the classpath list
226             ArrayList<IPath> sourceList = BaseProjectHelper.getSourceClasspaths(javaProject);
227 
228             // First thing we do is go through the resource delta to not
229             // lose it if we have to abort the build for any reason.
230             ApkDeltaVisitor dv = null;
231             if (kind == FULL_BUILD) {
232                 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
233                         Messages.Start_Full_Apk_Build);
234 
235                 mPackageResources = true;
236                 mConvertToDex = true;
237                 mBuildFinalPackage = true;
238             } else {
239                 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
240                         Messages.Start_Inc_Apk_Build);
241 
242                 // go through the resources and see if something changed.
243                 IResourceDelta delta = getDelta(project);
244                 if (delta == null) {
245                     mPackageResources = true;
246                     mConvertToDex = true;
247                     mBuildFinalPackage = true;
248                 } else {
249                     dv = new ApkDeltaVisitor(this, sourceList, outputFolder);
250                     delta.accept(dv);
251 
252                     // save the state
253                     mPackageResources |= dv.getPackageResources();
254                     mConvertToDex |= dv.getConvertToDex();
255                     mBuildFinalPackage |= dv.getMakeFinalPackage();
256                 }
257 
258                 // also go through the delta for all the referenced projects, until we are forced to
259                 // compile anyway
260                 for (int i = 0 ; i < referencedJavaProjects.length &&
261                         (mBuildFinalPackage == false || mConvertToDex == false); i++) {
262                     IJavaProject referencedJavaProject = referencedJavaProjects[i];
263                     delta = getDelta(referencedJavaProject.getProject());
264                     if (delta != null) {
265                         ReferencedProjectDeltaVisitor refProjectDv = new ReferencedProjectDeltaVisitor(
266                                 referencedJavaProject);
267                         delta.accept(refProjectDv);
268 
269                         // save the state
270                         mConvertToDex |= refProjectDv.needDexConvertion();
271                         mBuildFinalPackage |= refProjectDv.needMakeFinalPackage();
272                     }
273                 }
274             }
275 
276             // store the build status in the persistent storage
277             saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX , mConvertToDex);
278             saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
279             saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
280 
281             if (dv != null && dv.mXmlError) {
282                 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
283                 Messages.Xml_Error);
284 
285                 // if there was some XML errors, we just return w/o doing
286                 // anything since we've put some markers in the files anyway
287                 return referencedProjects;
288             }
289 
290             if (outputFolder == null) {
291                 // mark project and exit
292                 markProject(AdtConstants.MARKER_ADT, Messages.Failed_To_Get_Output,
293                         IMarker.SEVERITY_ERROR);
294                 return referencedProjects;
295             }
296 
297             // first thing we do is check that the SDK directory has been setup.
298             String osSdkFolder = AdtPlugin.getOsSdkFolder();
299 
300             if (osSdkFolder.length() == 0) {
301                 // this has already been checked in the precompiler. Therefore,
302                 // while we do have to cancel the build, we don't have to return
303                 // any error or throw anything.
304                 return referencedProjects;
305             }
306 
307             // get the APK configs for the project.
308             ApkSettings apkSettings = Sdk.getCurrent().getApkSettings(project);
309             Set<Entry<String, String>> apkfilters = null;
310             if (apkSettings != null) {
311                 Map<String, String> filterMap = apkSettings.getResourceFilters();
312                 if (filterMap != null && filterMap.size() > 0) {
313                     apkfilters = filterMap.entrySet();
314                 }
315             }
316 
317             // do some extra check, in case the output files are not present. This
318             // will force to recreate them.
319             IResource tmp = null;
320 
321             if (mPackageResources == false) {
322                 // check the full resource package
323                 tmp = outputFolder.findMember(AndroidConstants.FN_RESOURCES_AP_);
324                 if (tmp == null || tmp.exists() == false) {
325                     mPackageResources = true;
326                     mBuildFinalPackage = true;
327                 } else {
328                     // if the full package is present, we check the filtered resource packages
329                     // as well
330                     if (apkfilters != null) {
331                         for (Entry<String, String> entry : apkfilters) {
332                             String filename = String.format(AndroidConstants.FN_RESOURCES_S_AP_,
333                                     entry.getKey());
334 
335                             tmp = outputFolder.findMember(filename);
336                             if (tmp == null || (tmp instanceof IFile &&
337                                     tmp.exists() == false)) {
338                                 String msg = String.format(Messages.s_Missing_Repackaging,
339                                         filename);
340                                 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE,
341                                         project, msg);
342                                 mPackageResources = true;
343                                 mBuildFinalPackage = true;
344                                 break;
345                             }
346                         }
347                     }
348                 }
349             }
350 
351             // check classes.dex is present. If not we force to recreate it.
352             if (mConvertToDex == false) {
353                 tmp = outputFolder.findMember(AndroidConstants.FN_CLASSES_DEX);
354                 if (tmp == null || tmp.exists() == false) {
355                     mConvertToDex = true;
356                     mBuildFinalPackage = true;
357                 }
358             }
359 
360             // also check the final file(s)!
361             String finalPackageName = ProjectHelper.getApkFilename(project, null /*config*/);
362             if (mBuildFinalPackage == false) {
363                 tmp = outputFolder.findMember(finalPackageName);
364                 if (tmp == null || (tmp instanceof IFile &&
365                         tmp.exists() == false)) {
366                     String msg = String.format(Messages.s_Missing_Repackaging, finalPackageName);
367                     AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
368                     mBuildFinalPackage = true;
369                 } else if (apkfilters != null) {
370                     // if the full apk is present, we check the filtered apk as well
371                     for (Entry<String, String> entry : apkfilters) {
372                         String filename = ProjectHelper.getApkFilename(project, entry.getKey());
373 
374                         tmp = outputFolder.findMember(filename);
375                         if (tmp == null || (tmp instanceof IFile &&
376                                 tmp.exists() == false)) {
377                             String msg = String.format(Messages.s_Missing_Repackaging, filename);
378                             AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
379                             mBuildFinalPackage = true;
380                             break;
381                         }
382                     }
383                 }
384             }
385 
386             // at this point we know if we need to recreate the temporary apk
387             // or the dex file, but we don't know if we simply need to recreate them
388             // because they are missing
389 
390             // refresh the output directory first
391             IContainer ic = outputFolder.getParent();
392             if (ic != null) {
393                 ic.refreshLocal(IResource.DEPTH_ONE, monitor);
394             }
395 
396             // we need to test all three, as we may need to make the final package
397             // but not the intermediary ones.
398             if (mPackageResources || mConvertToDex || mBuildFinalPackage) {
399                 IPath binLocation = outputFolder.getLocation();
400                 if (binLocation == null) {
401                     markProject(AdtConstants.MARKER_ADT, Messages.Output_Missing,
402                             IMarker.SEVERITY_ERROR);
403                     return referencedProjects;
404                 }
405                 String osBinPath = binLocation.toOSString();
406 
407                 // Remove the old .apk.
408                 // This make sure that if the apk is corrupted, then dx (which would attempt
409                 // to open it), will not fail.
410                 String osFinalPackagePath = osBinPath + File.separator + finalPackageName;
411                 File finalPackage = new File(osFinalPackagePath);
412 
413                 // if delete failed, this is not really a problem, as the final package generation
414                 // handle already present .apk, and if that one failed as well, the user will be
415                 // notified.
416                 finalPackage.delete();
417 
418                 if (apkfilters != null) {
419                     for (Entry<String, String> entry : apkfilters) {
420                         String packageFilepath = osBinPath + File.separator +
421                                 ProjectHelper.getApkFilename(project, entry.getKey());
422 
423                         finalPackage = new File(packageFilepath);
424                         finalPackage.delete();
425                     }
426                 }
427 
428                 // first we check if we need to package the resources.
429                 if (mPackageResources) {
430                     // remove some aapt_package only markers.
431                     removeMarkersFromContainer(project, AndroidConstants.MARKER_AAPT_PACKAGE);
432 
433                     // need to figure out some path before we can execute aapt;
434 
435                     // resource to the AndroidManifest.xml file
436                     IResource manifestResource = project .findMember(
437                             AndroidConstants.WS_SEP + AndroidConstants.FN_ANDROID_MANIFEST);
438 
439                     if (manifestResource == null
440                             || manifestResource.exists() == false) {
441                         // mark project and exit
442                         String msg = String.format(Messages.s_File_Missing,
443                                 AndroidConstants.FN_ANDROID_MANIFEST);
444                         markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
445                         return referencedProjects;
446                     }
447 
448                     // get the resource folder
449                     IFolder resFolder = project.getFolder(
450                             AndroidConstants.WS_RESOURCES);
451 
452                     // and the assets folder
453                     IFolder assetsFolder = project.getFolder(
454                             AndroidConstants.WS_ASSETS);
455 
456                     // we need to make sure this one exists.
457                     if (assetsFolder.exists() == false) {
458                         assetsFolder = null;
459                     }
460 
461                     IPath resLocation = resFolder.getLocation();
462                     IPath manifestLocation = manifestResource.getLocation();
463 
464                     if (resLocation != null && manifestLocation != null) {
465                         String osResPath = resLocation.toOSString();
466                         String osManifestPath = manifestLocation.toOSString();
467 
468                         String osAssetsPath = null;
469                         if (assetsFolder != null) {
470                             osAssetsPath = assetsFolder.getLocation().toOSString();
471                         }
472 
473                         // build the default resource package
474                         if (executeAapt(project, osManifestPath, osResPath,
475                                 osAssetsPath, osBinPath + File.separator +
476                                 AndroidConstants.FN_RESOURCES_AP_, null /*configFilter*/) == false) {
477                             // aapt failed. Whatever files that needed to be marked
478                             // have already been marked. We just return.
479                             return referencedProjects;
480                         }
481 
482                         // now do the same thing for all the configured resource packages.
483                         if (apkfilters != null) {
484                             for (Entry<String, String> entry : apkfilters) {
485                                 String outPathFormat = osBinPath + File.separator +
486                                         AndroidConstants.FN_RESOURCES_S_AP_;
487                                 String outPath = String.format(outPathFormat, entry.getKey());
488                                 if (executeAapt(project, osManifestPath, osResPath,
489                                         osAssetsPath, outPath, entry.getValue()) == false) {
490                                     // aapt failed. Whatever files that needed to be marked
491                                     // have already been marked. We just return.
492                                     return referencedProjects;
493                                 }
494                             }
495                         }
496 
497                         // build has been done. reset the state of the builder
498                         mPackageResources = false;
499 
500                         // and store it
501                         saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
502                     }
503                 }
504 
505                 // then we check if we need to package the .class into classes.dex
506                 if (mConvertToDex) {
507                     if (executeDx(javaProject, osBinPath, osBinPath + File.separator +
508                             AndroidConstants.FN_CLASSES_DEX, referencedJavaProjects) == false) {
509                         // dx failed, we return
510                         return referencedProjects;
511                     }
512 
513                     // build has been done. reset the state of the builder
514                     mConvertToDex = false;
515 
516                     // and store it
517                     saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex);
518                 }
519 
520                 // now we need to make the final package from the intermediary apk
521                 // and classes.dex.
522                 // This is the default package with all the resources.
523 
524                 String classesDexPath = osBinPath + File.separator + AndroidConstants.FN_CLASSES_DEX;
525                 if (finalPackage(osBinPath + File.separator + AndroidConstants.FN_RESOURCES_AP_,
526                                 classesDexPath,osFinalPackagePath, javaProject,
527                                 referencedJavaProjects) == false) {
528                     return referencedProjects;
529                 }
530 
531                 // now do the same thing for all the configured resource packages.
532                 if (apkfilters != null) {
533                     String resPathFormat = osBinPath + File.separator +
534                             AndroidConstants.FN_RESOURCES_S_AP_;
535 
536                     for (Entry<String, String> entry : apkfilters) {
537                         // make the filename for the resource package.
538                         String resPath = String.format(resPathFormat, entry.getKey());
539 
540                         // make the filename for the apk to generate
541                         String apkOsFilePath = osBinPath + File.separator +
542                                 ProjectHelper.getApkFilename(project, entry.getKey());
543                         if (finalPackage(resPath, classesDexPath, apkOsFilePath, javaProject,
544                                 referencedJavaProjects) == false) {
545                             return referencedProjects;
546                         }
547                     }
548                 }
549 
550                 // we are done.
551 
552                 // get the resource to bin
553                 outputFolder.refreshLocal(IResource.DEPTH_ONE, monitor);
554 
555                 // build has been done. reset the state of the builder
556                 mBuildFinalPackage = false;
557 
558                 // and store it
559                 saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
560 
561                 // reset the installation manager to force new installs of this project
562                 ApkInstallManager.getInstance().resetInstallationFor(project);
563 
564                 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(),
565                         "Build Success!");
566             }
567         } catch (Exception exception) {
568             // try to catch other exception to actually display an error. This will be useful
569             // if we get an NPE or something so that we can at least notify the user that something
570             // went wrong.
571 
572             // first check if this is a CoreException we threw to cancel the build.
573             if (exception instanceof CoreException) {
574                 if (((CoreException)exception).getStatus().getSeverity() == IStatus.CANCEL) {
575                     // Project is already marked with an error. Nothing to do
576                     return referencedProjects;
577                 }
578             }
579 
580             String msg = exception.getMessage();
581             if (msg == null) {
582                 msg = exception.getClass().getCanonicalName();
583             }
584 
585             msg = String.format("Unknown error: %1$s", msg);
586             AdtPlugin.printErrorToConsole(project, msg);
587             markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
588         }
589 
590         return referencedProjects;
591     }
592 
593     @Override
startupOnInitialize()594     protected void startupOnInitialize() {
595         super.startupOnInitialize();
596 
597         // load the build status. We pass true as the default value to
598         // force a recompile in case the property was not found
599         mConvertToDex = loadProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX , true);
600         mPackageResources = loadProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, true);
601         mBuildFinalPackage = loadProjectBooleanProperty(PROPERTY_BUILD_APK, true);
602     }
603 
604     /**
605      * Executes aapt. If any error happen, files or the project will be marked.
606      * @param project The Project
607      * @param osManifestPath The path to the manifest file
608      * @param osResPath The path to the res folder
609      * @param osAssetsPath The path to the assets folder. This can be null.
610      * @param osOutFilePath The path to the temporary resource file to create.
611      * @param configFilter The configuration filter for the resources to include
612      * (used with -c option, for example "port,en,fr" to include portrait, English and French
613      * resources.)
614      * @return true if success, false otherwise.
615      */
executeAapt(IProject project, String osManifestPath, String osResPath, String osAssetsPath, String osOutFilePath, String configFilter)616     private boolean executeAapt(IProject project, String osManifestPath,
617             String osResPath, String osAssetsPath, String osOutFilePath, String configFilter) {
618         IAndroidTarget target = Sdk.getCurrent().getTarget(project);
619 
620         // Create the command line.
621         ArrayList<String> commandArray = new ArrayList<String>();
622         commandArray.add(target.getPath(IAndroidTarget.AAPT));
623         commandArray.add("package"); //$NON-NLS-1$
624         commandArray.add("-f");//$NON-NLS-1$
625         if (AdtPlugin.getBuildVerbosity() == AdtConstants.BUILD_VERBOSE) {
626             commandArray.add("-v"); //$NON-NLS-1$
627         }
628         if (configFilter != null) {
629             commandArray.add("-c"); //$NON-NLS-1$
630             commandArray.add(configFilter);
631         }
632         commandArray.add("-M"); //$NON-NLS-1$
633         commandArray.add(osManifestPath);
634         commandArray.add("-S"); //$NON-NLS-1$
635         commandArray.add(osResPath);
636         if (osAssetsPath != null) {
637             commandArray.add("-A"); //$NON-NLS-1$
638             commandArray.add(osAssetsPath);
639         }
640         commandArray.add("-I"); //$NON-NLS-1$
641         commandArray.add(target.getPath(IAndroidTarget.ANDROID_JAR));
642         commandArray.add("-F"); //$NON-NLS-1$
643         commandArray.add(osOutFilePath);
644 
645         String command[] = commandArray.toArray(
646                 new String[commandArray.size()]);
647 
648         if (AdtPlugin.getBuildVerbosity() == AdtConstants.BUILD_VERBOSE) {
649             StringBuilder sb = new StringBuilder();
650             for (String c : command) {
651                 sb.append(c);
652                 sb.append(' ');
653             }
654             AdtPlugin.printToConsole(project, sb.toString());
655         }
656 
657         // launch
658         int execError = 1;
659         try {
660             // launch the command line process
661             Process process = Runtime.getRuntime().exec(command);
662 
663             // list to store each line of stderr
664             ArrayList<String> results = new ArrayList<String>();
665 
666             // get the output and return code from the process
667             execError = grabProcessOutput(process, results);
668 
669             // attempt to parse the error output
670             boolean parsingError = parseAaptOutput(results, project);
671 
672             // if we couldn't parse the output we display it in the console.
673             if (parsingError) {
674                 if (execError != 0) {
675                     AdtPlugin.printErrorToConsole(project, results.toArray());
676                 } else {
677                     AdtPlugin.printBuildToConsole(AdtConstants.BUILD_ALWAYS, project,
678                             results.toArray());
679                 }
680             }
681 
682             // We need to abort if the exec failed.
683             if (execError != 0) {
684                 // if the exec failed, and we couldn't parse the error output (and therefore
685                 // not all files that should have been marked, were marked), we put a generic
686                 // marker on the project and abort.
687                 if (parsingError) {
688                     markProject(AdtConstants.MARKER_ADT, Messages.Unparsed_AAPT_Errors,
689                             IMarker.SEVERITY_ERROR);
690                 }
691 
692                 // abort if exec failed.
693                 return false;
694             }
695         } catch (IOException e1) {
696             String msg = String.format(Messages.AAPT_Exec_Error, command[0]);
697             markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
698             return false;
699         } catch (InterruptedException e) {
700             String msg = String.format(Messages.AAPT_Exec_Error, command[0]);
701             markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
702             return false;
703         }
704 
705         return true;
706     }
707 
708     /**
709      * Execute the Dx tool for dalvik code conversion.
710      * @param javaProject The java project
711      * @param osBinPath the path to the output folder of the project
712      * @param osOutFilePath the path of the dex file to create.
713      * @param referencedJavaProjects the list of referenced projects for this project.
714      *
715      * @throws CoreException
716      */
executeDx(IJavaProject javaProject, String osBinPath, String osOutFilePath, IJavaProject[] referencedJavaProjects)717     private boolean executeDx(IJavaProject javaProject, String osBinPath, String osOutFilePath,
718             IJavaProject[] referencedJavaProjects) throws CoreException {
719         IAndroidTarget target = Sdk.getCurrent().getTarget(javaProject.getProject());
720         AndroidTargetData targetData = Sdk.getCurrent().getTargetData(target);
721         if (targetData == null) {
722             throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
723                     Messages.ApkBuilder_UnableBuild_Dex_Not_loaded));
724         }
725 
726         // get the dex wrapper
727         DexWrapper wrapper = targetData.getDexWrapper();
728 
729         if (wrapper == null) {
730             throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
731                     Messages.ApkBuilder_UnableBuild_Dex_Not_loaded));
732         }
733 
734         // make sure dx use the proper output streams.
735         // first make sure we actually have the streams available.
736         if (mOutStream == null) {
737             IProject project = getProject();
738             mOutStream = AdtPlugin.getOutPrintStream(project, DX_PREFIX);
739             mErrStream = AdtPlugin.getErrPrintStream(project, DX_PREFIX);
740         }
741 
742         try {
743             // get the list of libraries to include with the source code
744             String[] libraries = getExternalJars();
745 
746             // get the list of referenced projects output to add
747             String[] projectOutputs = getProjectOutputs(referencedJavaProjects);
748 
749             String[] fileNames = new String[1 + projectOutputs.length + libraries.length];
750 
751             // first this project output
752             fileNames[0] = osBinPath;
753 
754             // then other project output
755             System.arraycopy(projectOutputs, 0, fileNames, 1, projectOutputs.length);
756 
757             // then external jars.
758             System.arraycopy(libraries, 0, fileNames, 1 + projectOutputs.length, libraries.length);
759 
760             int res = wrapper.run(osOutFilePath, fileNames,
761                     AdtPlugin.getBuildVerbosity() == AdtConstants.BUILD_VERBOSE,
762                     mOutStream, mErrStream);
763 
764             if (res != 0) {
765                 // output error message and marker the project.
766                 String message = String.format(Messages.Dalvik_Error_d,
767                         res);
768                 AdtPlugin.printErrorToConsole(getProject(), message);
769                 markProject(AdtConstants.MARKER_ADT, message, IMarker.SEVERITY_ERROR);
770                 return false;
771             }
772         } catch (Throwable ex) {
773             String message = ex.getMessage();
774             if (message == null) {
775                 message = ex.getClass().getCanonicalName();
776             }
777             message = String.format(Messages.Dalvik_Error_s, message);
778             AdtPlugin.printErrorToConsole(getProject(), message);
779             markProject(AdtConstants.MARKER_ADT, message, IMarker.SEVERITY_ERROR);
780             if ((ex instanceof NoClassDefFoundError)
781                     || (ex instanceof NoSuchMethodError)) {
782                 AdtPlugin.printErrorToConsole(getProject(), Messages.Incompatible_VM_Warning,
783                         Messages.Requires_1_5_Error);
784             }
785             return false;
786         }
787 
788         return true;
789     }
790 
791     /**
792      * Makes the final package. Package the dex files, the temporary resource file into the final
793      * package file.
794      * @param intermediateApk The path to the temporary resource file.
795      * @param dex The path to the dex file.
796      * @param output The path to the final package file to create.
797      * @param javaProject
798      * @param referencedJavaProjects
799      * @return true if success, false otherwise.
800      */
finalPackage(String intermediateApk, String dex, String output, final IJavaProject javaProject, IJavaProject[] referencedJavaProjects)801     private boolean finalPackage(String intermediateApk, String dex, String output,
802             final IJavaProject javaProject, IJavaProject[] referencedJavaProjects) {
803         FileOutputStream fos = null;
804         try {
805             IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
806             String osKeyPath = store.getString(AdtPlugin.PREFS_CUSTOM_DEBUG_KEYSTORE);
807             if (osKeyPath == null || new File(osKeyPath).exists() == false) {
808                 osKeyPath = DebugKeyProvider.getDefaultKeyStoreOsPath();
809                 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(),
810                         Messages.ApkBuilder_Using_Default_Key);
811             } else {
812                 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(),
813                         String.format(Messages.ApkBuilder_Using_s_To_Sign, osKeyPath));
814             }
815 
816             // TODO: get the store type from somewhere else.
817             DebugKeyProvider provider = new DebugKeyProvider(osKeyPath, null /* storeType */,
818                     new IKeyGenOutput() {
819                         public void err(String message) {
820                             AdtPlugin.printErrorToConsole(javaProject.getProject(),
821                                     Messages.ApkBuilder_Signing_Key_Creation_s + message);
822                         }
823 
824                         public void out(String message) {
825                             AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE,
826                                     javaProject.getProject(),
827                                     Messages.ApkBuilder_Signing_Key_Creation_s + message);
828                         }
829             });
830             PrivateKey key = provider.getDebugKey();
831             X509Certificate certificate = (X509Certificate)provider.getCertificate();
832 
833             if (key == null) {
834                 String msg = String.format(Messages.Final_Archive_Error_s,
835                         Messages.ApkBuilder_Unable_To_Gey_Key);
836                 AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
837                 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
838                 return false;
839             }
840 
841             // compare the certificate expiration date
842             if (certificate != null && certificate.getNotAfter().compareTo(new Date()) < 0) {
843                 // TODO, regenerate a new one.
844                 String msg = String.format(Messages.Final_Archive_Error_s,
845                     String.format(Messages.ApkBuilder_Certificate_Expired_on_s,
846                             DateFormat.getInstance().format(certificate.getNotAfter())));
847                 AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
848                 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
849                 return false;
850             }
851 
852             // create the jar builder.
853             fos = new FileOutputStream(output);
854             SignedJarBuilder builder = new SignedJarBuilder(fos, key, certificate);
855 
856             // add the intermediate file containing the compiled resources.
857             AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(),
858                     String.format(Messages.ApkBuilder_Packaging_s, intermediateApk));
859             FileInputStream fis = new FileInputStream(intermediateApk);
860             try {
861                 builder.writeZip(fis, null /* filter */);
862             } finally {
863                 fis.close();
864             }
865 
866             // Now we add the new file to the zip archive for the classes.dex file.
867             AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(),
868                     String.format(Messages.ApkBuilder_Packaging_s, AndroidConstants.FN_CLASSES_DEX));
869             File entryFile = new File(dex);
870             builder.writeFile(entryFile, AndroidConstants.FN_CLASSES_DEX);
871 
872             // Now we write the standard resources from the project and the referenced projects.
873             writeStandardResources(builder, javaProject, referencedJavaProjects);
874 
875             // Now we write the standard resources from the external libraries
876             for (String libraryOsPath : getExternalJars()) {
877                 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(),
878                         String.format(Messages.ApkBuilder_Packaging_s, libraryOsPath));
879                 try {
880                     fis = new FileInputStream(libraryOsPath);
881                     builder.writeZip(fis, mJavaResourcesFilter);
882                 } finally {
883                     fis.close();
884                 }
885             }
886 
887             // now write the native libraries.
888             // First look if the lib folder is there.
889             IResource libFolder = javaProject.getProject().findMember(SdkConstants.FD_NATIVE_LIBS);
890             if (libFolder != null && libFolder.exists() &&
891                     libFolder.getType() == IResource.FOLDER) {
892                 // look inside and put .so in lib/* by keeping the relative folder path.
893                 writeNativeLibraries(libFolder.getFullPath().segmentCount(), builder, libFolder);
894             }
895 
896             // close the jar file and write the manifest and sign it.
897             builder.close();
898         } catch (GeneralSecurityException e1) {
899             // mark project and return
900             String msg = String.format(Messages.Final_Archive_Error_s, e1.getMessage());
901             AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
902             markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
903             return false;
904         } catch (IOException e1) {
905             // mark project and return
906             String msg = String.format(Messages.Final_Archive_Error_s, e1.getMessage());
907             AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
908             markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
909             return false;
910         } catch (KeytoolException e) {
911             String eMessage = e.getMessage();
912 
913             // mark the project with the standard message
914             String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
915             markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
916 
917             // output more info in the console
918             AdtPlugin.printErrorToConsole(javaProject.getProject(),
919                     msg,
920                     String.format(Messages.ApkBuilder_JAVA_HOME_is_s, e.getJavaHome()),
921                     Messages.ApkBuilder_Update_or_Execute_manually_s,
922                     e.getCommandLine());
923         } catch (AndroidLocationException e) {
924             String eMessage = e.getMessage();
925 
926             // mark the project with the standard message
927             String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
928             markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
929 
930             // and also output it in the console
931             AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
932         } catch (CoreException e) {
933             // mark project and return
934             String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage());
935             AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
936             markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
937             return false;
938         } catch (Exception e) {
939             // try to catch other exception to actually display an error. This will be useful
940             // if we get an NPE or something so that we can at least notify the user that something
941             // went wrong (otherwise the build appears to succeed but the zip archive is not closed
942             // and therefore invalid.
943             String msg = e.getMessage();
944             if (msg == null) {
945                 msg = e.getClass().getCanonicalName();
946             }
947 
948             msg = String.format("Unknown error: %1$s", msg);
949             AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
950             markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
951             return false;
952         } finally {
953             if (fos != null) {
954                 try {
955                     fos.close();
956                 } catch (IOException e) {
957                     // pass.
958                 }
959             }
960         }
961 
962         return true;
963     }
964 
965     /**
966      * Writes native libraries into a {@link SignedJarBuilder}.
967      * <p/>This recursively go through folder and writes .so files.
968      * The path in the archive is based on the root folder containing the libraries in the project.
969      * Its segment count is passed to the method to compute the resources path relative to the root
970      * folder.
971      * Native libraries in the archive must be in a "lib" folder. Everything in the project native
972      * lib folder directly goes in this "lib" folder in the archive.
973      *
974      *
975      * @param rootSegmentCount The number of segment of the path of the folder containing the
976      * libraries. This is used to compute the path in the archive.
977      * @param jarBuilder the {@link SignedJarBuilder} used to create the archive.
978      * @param resource the IResource to write.
979      * @throws CoreException
980      * @throws IOException
981      */
writeNativeLibraries(int rootSegmentCount, SignedJarBuilder jarBuilder, IResource resource)982     private void writeNativeLibraries(int rootSegmentCount, SignedJarBuilder jarBuilder,
983             IResource resource) throws CoreException, IOException {
984         if (resource.getType() == IResource.FILE) {
985             IPath path = resource.getFullPath();
986 
987             // check the extension.
988             String ext = path.getFileExtension();
989             if (ext != null && ext.equalsIgnoreCase(AndroidConstants.EXT_NATIVE_LIB)) {
990                 // remove the first segment to build the path inside the archive.
991                 path = path.removeFirstSegments(rootSegmentCount);
992 
993                 // add it to the archive.
994                 IPath apkPath = new Path(SdkConstants.FD_APK_NATIVE_LIBS);
995                 apkPath = apkPath.append(path);
996 
997                 // writes the file in the apk.
998                 jarBuilder.writeFile(resource.getLocation().toFile(), apkPath.toString());
999             }
1000         } else if (resource.getType() == IResource.FOLDER &&
1001                 checkFolderForPackaging((IFolder)resource)) {
1002             IResource[] members = ((IFolder)resource).members();
1003             for (IResource member : members) {
1004                 writeNativeLibraries(rootSegmentCount, jarBuilder, member);
1005             }
1006         }
1007     }
1008 
1009     /**
1010      * Writes the standard resources of a project and its referenced projects
1011      * into a {@link SignedJarBuilder}.
1012      * Standard resources are non java/aidl files placed in the java package folders.
1013      * @param jarBuilder the {@link SignedJarBuilder}.
1014      * @param javaProject the javaProject object.
1015      * @param referencedJavaProjects the java projects that this project references.
1016      * @throws IOException
1017      * @throws CoreException
1018      */
writeStandardResources(SignedJarBuilder jarBuilder, IJavaProject javaProject, IJavaProject[] referencedJavaProjects)1019     private void writeStandardResources(SignedJarBuilder jarBuilder, IJavaProject javaProject,
1020             IJavaProject[] referencedJavaProjects) throws IOException, CoreException {
1021         IWorkspace ws = ResourcesPlugin.getWorkspace();
1022         IWorkspaceRoot wsRoot = ws.getRoot();
1023 
1024         // create a list of path already put into the archive, in order to detect conflict
1025         ArrayList<String> list = new ArrayList<String>();
1026 
1027         writeStandardProjectResources(jarBuilder, javaProject, wsRoot, list);
1028 
1029         for (IJavaProject referencedJavaProject : referencedJavaProjects) {
1030             // only include output from non android referenced project
1031             // (This is to handle the case of reference Android projects in the context of
1032             // instrumentation projects that need to reference the projects to be tested).
1033             if (referencedJavaProject.getProject().hasNature(AndroidConstants.NATURE) == false) {
1034                 writeStandardProjectResources(jarBuilder, referencedJavaProject, wsRoot, list);
1035             }
1036         }
1037     }
1038 
1039     /**
1040      * Writes the standard resources of a {@link IJavaProject} into a {@link SignedJarBuilder}.
1041      * Standard resources are non java/aidl files placed in the java package folders.
1042      * @param jarBuilder the {@link SignedJarBuilder}.
1043      * @param javaProject the javaProject object.
1044      * @param wsRoot the {@link IWorkspaceRoot}.
1045      * @param list a list of files already added to the archive, to detect conflicts.
1046      * @throws IOException
1047      */
writeStandardProjectResources(SignedJarBuilder jarBuilder, IJavaProject javaProject, IWorkspaceRoot wsRoot, ArrayList<String> list)1048     private void writeStandardProjectResources(SignedJarBuilder jarBuilder,
1049             IJavaProject javaProject, IWorkspaceRoot wsRoot, ArrayList<String> list)
1050             throws IOException {
1051         // get the source pathes
1052         ArrayList<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths(javaProject);
1053 
1054         // loop on them and then recursively go through the content looking for matching files.
1055         for (IPath sourcePath : sourceFolders) {
1056             IResource sourceResource = wsRoot.findMember(sourcePath);
1057             if (sourceResource != null && sourceResource.getType() == IResource.FOLDER) {
1058                 writeStandardSourceFolderResources(jarBuilder, sourcePath, (IFolder)sourceResource,
1059                         list);
1060             }
1061         }
1062     }
1063 
1064     /**
1065      * Recursively writes the standard resources of a source folder into a {@link SignedJarBuilder}.
1066      * Standard resources are non java/aidl files placed in the java package folders.
1067      * @param jarBuilder the {@link SignedJarBuilder}.
1068      * @param sourceFolder the {@link IPath} of the source folder.
1069      * @param currentFolder The current folder we're recursively processing.
1070      * @param list a list of files already added to the archive, to detect conflicts.
1071      * @throws IOException
1072      */
writeStandardSourceFolderResources(SignedJarBuilder jarBuilder, IPath sourceFolder, IFolder currentFolder, ArrayList<String> list)1073     private void writeStandardSourceFolderResources(SignedJarBuilder jarBuilder, IPath sourceFolder,
1074             IFolder currentFolder, ArrayList<String> list) throws IOException {
1075         try {
1076             IResource[] members = currentFolder.members();
1077 
1078             for (IResource member : members) {
1079                 int type = member.getType();
1080                 if (type == IResource.FILE && member.exists()) {
1081                     if (checkFileForPackaging((IFile)member)) {
1082                         // this files must be added to the archive.
1083                         IPath fullPath = member.getFullPath();
1084 
1085                         // We need to create its path inside the archive.
1086                         // This path is relative to the source folder.
1087                         IPath relativePath = fullPath.removeFirstSegments(
1088                                 sourceFolder.segmentCount());
1089                         String zipPath = relativePath.toString();
1090 
1091                         // lets check it's not already in the list of path added to the archive
1092                         if (list.indexOf(zipPath) != -1) {
1093                             AdtPlugin.printErrorToConsole(getProject(),
1094                                     String.format(
1095                                             Messages.ApkBuilder_s_Conflict_with_file_s,
1096                                             fullPath, zipPath));
1097                         } else {
1098                             // get the File object
1099                             File entryFile = member.getLocation().toFile();
1100 
1101                             AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(),
1102                                     String.format(Messages.ApkBuilder_Packaging_s_into_s, fullPath, zipPath));
1103 
1104                             // write it in the zip archive
1105                             jarBuilder.writeFile(entryFile, zipPath);
1106 
1107                             // and add it to the list of entries
1108                             list.add(zipPath);
1109                         }
1110                     }
1111                 } else if (type == IResource.FOLDER) {
1112                     if (checkFolderForPackaging((IFolder)member)) {
1113                         writeStandardSourceFolderResources(jarBuilder, sourceFolder,
1114                                 (IFolder)member, list);
1115                     }
1116                 }
1117             }
1118         } catch (CoreException e) {
1119             // if we can't get the members of the folder, we just don't do anything.
1120         }
1121     }
1122 
1123     /**
1124      * Returns the list of the output folders for the specified {@link IJavaProject} objects, if
1125      * they are Android projects.
1126      *
1127      * @param referencedJavaProjects the java projects.
1128      * @return an array, always. Can be empty.
1129      * @throws CoreException
1130      */
getProjectOutputs(IJavaProject[] referencedJavaProjects)1131     private String[] getProjectOutputs(IJavaProject[] referencedJavaProjects) throws CoreException {
1132         ArrayList<String> list = new ArrayList<String>();
1133 
1134         IWorkspace ws = ResourcesPlugin.getWorkspace();
1135         IWorkspaceRoot wsRoot = ws.getRoot();
1136 
1137         for (IJavaProject javaProject : referencedJavaProjects) {
1138             // only include output from non android referenced project
1139             // (This is to handle the case of reference Android projects in the context of
1140             // instrumentation projects that need to reference the projects to be tested).
1141             if (javaProject.getProject().hasNature(AndroidConstants.NATURE) == false) {
1142                 // get the output folder
1143                 IPath path = null;
1144                 try {
1145                     path = javaProject.getOutputLocation();
1146                 } catch (JavaModelException e) {
1147                     continue;
1148                 }
1149 
1150                 IResource outputResource = wsRoot.findMember(path);
1151                 if (outputResource != null && outputResource.getType() == IResource.FOLDER) {
1152                     String outputOsPath = outputResource.getLocation().toOSString();
1153 
1154                     list.add(outputOsPath);
1155                 }
1156             }
1157         }
1158 
1159         return list.toArray(new String[list.size()]);
1160     }
1161 
1162     /**
1163      * Returns an array of {@link IJavaProject} matching the provided {@link IProject} objects.
1164      * @param projects the IProject objects.
1165      * @return an array, always. Can be empty.
1166      * @throws CoreException
1167      */
getJavaProjects(IProject[] projects)1168     private IJavaProject[] getJavaProjects(IProject[] projects) throws CoreException {
1169         ArrayList<IJavaProject> list = new ArrayList<IJavaProject>();
1170 
1171         for (IProject p : projects) {
1172             if (p.isOpen() && p.hasNature(JavaCore.NATURE_ID)) {
1173 
1174                 list.add(JavaCore.create(p));
1175             }
1176         }
1177 
1178         return list.toArray(new IJavaProject[list.size()]);
1179     }
1180 
1181     /**
1182      * Checks a {@link IFile} to make sure it should be packaged as standard resources.
1183      * @param file the IFile representing the file.
1184      * @return true if the file should be packaged as standard java resources.
1185      */
checkFileForPackaging(IFile file)1186     static boolean checkFileForPackaging(IFile file) {
1187         String name = file.getName();
1188 
1189         String ext = file.getFileExtension();
1190         return JavaResourceFilter.checkFileForPackaging(name, ext);
1191     }
1192 
1193     /**
1194      * Checks whether an {@link IFolder} and its content is valid for packaging into the .apk as
1195      * standard Java resource.
1196      * @param folder the {@link IFolder} to check.
1197      */
checkFolderForPackaging(IFolder folder)1198     static boolean checkFolderForPackaging(IFolder folder) {
1199         String name = folder.getName();
1200         return JavaResourceFilter.checkFolderForPackaging(name);
1201     }
1202 
1203     @Override
abortOnBadSetup(IProject project)1204     protected void abortOnBadSetup(IProject project) throws CoreException {
1205         super.abortOnBadSetup(project);
1206 
1207         // for this version, we stop on any marker (ie also markers coming from JDT)
1208         IMarker[] markers = project.findMarkers(null /*type*/, false /*includeSubtypes*/,
1209                 IResource.DEPTH_ZERO);
1210 
1211         if (markers.length > 0) {
1212             stopBuild("");
1213         }
1214     }
1215 }
1216