• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.sdklib.build;
18 
19 import com.android.sdklib.SdkConstants;
20 import com.android.sdklib.internal.build.DebugKeyProvider;
21 import com.android.sdklib.internal.build.SignedJarBuilder;
22 import com.android.sdklib.internal.build.DebugKeyProvider.IKeyGenOutput;
23 import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
24 import com.android.sdklib.internal.build.SignedJarBuilder.IZipEntryFilter;
25 
26 import java.io.File;
27 import java.io.FileInputStream;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.PrintStream;
31 import java.security.PrivateKey;
32 import java.security.cert.X509Certificate;
33 import java.text.DateFormat;
34 import java.util.ArrayList;
35 import java.util.Date;
36 import java.util.HashMap;
37 import java.util.List;
38 import java.util.regex.Pattern;
39 
40 /**
41  * Class making the final apk packaging.
42  * The inputs are:
43  * - packaged resources (output of aapt)
44  * - code file (ouput of dx)
45  * - Java resources coming from the project, its libraries, and its jar files
46  * - Native libraries from the project or its library.
47  *
48  */
49 public final class ApkBuilder {
50 
51     private final static Pattern PATTERN_NATIVELIB_EXT = Pattern.compile("^.+\\.so$",
52             Pattern.CASE_INSENSITIVE);
53 
54     /**
55      * A No-op zip filter. It's used to detect conflicts.
56      *
57      */
58     private final class NullZipFilter implements IZipEntryFilter {
59         private File mInputFile;
60 
reset(File inputFile)61         void reset(File inputFile) {
62             mInputFile = inputFile;
63         }
64 
checkEntry(String archivePath)65         public boolean checkEntry(String archivePath) throws ZipAbortException {
66             verbosePrintln("=> %s", archivePath);
67 
68             File duplicate = checkFileForDuplicate(archivePath);
69             if (duplicate != null) {
70                 throw new DuplicateFileException(archivePath, duplicate, mInputFile);
71             } else {
72                 mAddedFiles.put(archivePath, mInputFile);
73             }
74 
75             return true;
76         }
77     }
78 
79     /**
80      * Custom {@link IZipEntryFilter} to filter out everything that is not a standard java
81      * resources, and also record whether the zip file contains native libraries.
82      * <p/>Used in {@link SignedJarBuilder#writeZip(java.io.InputStream, IZipEntryFilter)} when
83      * we only want the java resources from external jars.
84      */
85     private final class JavaAndNativeResourceFilter implements IZipEntryFilter {
86         private final List<String> mNativeLibs = new ArrayList<String>();
87         private boolean mNativeLibsConflict = false;
88         private File mInputFile;
89 
checkEntry(String archivePath)90         public boolean checkEntry(String archivePath) throws ZipAbortException {
91             // split the path into segments.
92             String[] segments = archivePath.split("/");
93 
94             // empty path? skip to next entry.
95             if (segments.length == 0) {
96                 return false;
97             }
98 
99             // Check each folders to make sure they should be included.
100             // Folders like CVS, .svn, etc.. should already have been excluded from the
101             // jar file, but we need to exclude some other folder (like /META-INF) so
102             // we check anyway.
103             for (int i = 0 ; i < segments.length - 1; i++) {
104                 if (checkFolderForPackaging(segments[i]) == false) {
105                     return false;
106                 }
107             }
108 
109             // get the file name from the path
110             String fileName = segments[segments.length-1];
111 
112             boolean check = checkFileForPackaging(fileName);
113 
114             // only do additional checks if the file passes the default checks.
115             if (check) {
116                 verbosePrintln("=> %s", archivePath);
117 
118                 File duplicate = checkFileForDuplicate(archivePath);
119                 if (duplicate != null) {
120                     throw new DuplicateFileException(archivePath, duplicate, mInputFile);
121                 } else {
122                     mAddedFiles.put(archivePath, mInputFile);
123                 }
124 
125                 if (archivePath.endsWith(".so")) {
126                     mNativeLibs.add(archivePath);
127 
128                     // only .so located in lib/ will interfere with the installation
129                     if (archivePath.startsWith(SdkConstants.FD_APK_NATIVE_LIBS + "/")) {
130                         mNativeLibsConflict = true;
131                     }
132                 } else if (archivePath.endsWith(".jnilib")) {
133                     mNativeLibs.add(archivePath);
134                 }
135             }
136 
137             return check;
138         }
139 
getNativeLibs()140         List<String> getNativeLibs() {
141             return mNativeLibs;
142         }
143 
getNativeLibsConflict()144         boolean getNativeLibsConflict() {
145             return mNativeLibsConflict;
146         }
147 
reset(File inputFile)148         void reset(File inputFile) {
149             mInputFile = inputFile;
150             mNativeLibs.clear();
151             mNativeLibsConflict = false;
152         }
153     }
154 
155     private final File mApkFile;
156     private final File mResFile;
157     private final File mDexFile;
158     private final PrintStream mVerboseStream;
159     private final SignedJarBuilder mBuilder;
160     private boolean mDebugMode = false;
161     private boolean mIsSealed = false;
162 
163     private final NullZipFilter mNullFilter = new NullZipFilter();
164     private final JavaAndNativeResourceFilter mFilter = new JavaAndNativeResourceFilter();
165     private final HashMap<String, File> mAddedFiles = new HashMap<String, File>();
166 
167     /**
168      * Status for the addition of a jar file resources into the APK.
169      * This indicates possible issues with native library inside the jar file.
170      */
171     public interface JarStatus {
172         /**
173          * Returns the list of native libraries found in the jar file.
174          */
getNativeLibs()175         List<String> getNativeLibs();
176 
177         /**
178          * Returns whether some of those libraries were located in the location that Android
179          * expects its native libraries.
180          */
hasNativeLibsConflicts()181         boolean hasNativeLibsConflicts();
182 
183     }
184 
185     /** Internal implementation of {@link JarStatus}. */
186     private final static class JarStatusImpl implements JarStatus {
187         public final List<String> mLibs;
188         public final boolean mNativeLibsConflict;
189 
JarStatusImpl(List<String> libs, boolean nativeLibsConflict)190         private JarStatusImpl(List<String> libs, boolean nativeLibsConflict) {
191             mLibs = libs;
192             mNativeLibsConflict = nativeLibsConflict;
193         }
194 
getNativeLibs()195         public List<String> getNativeLibs() {
196             return mLibs;
197         }
198 
hasNativeLibsConflicts()199         public boolean hasNativeLibsConflicts() {
200             return mNativeLibsConflict;
201         }
202     }
203 
204     /**
205      * Creates a new instance.
206      * @param apkOsPath the OS path of the file to create.
207      * @param resOsPath the OS path of the packaged resource file.
208      * @param dexOsPath the OS path of the dex file. This can be null for apk with no code.
209      * @throws ApkCreationException
210      */
ApkBuilder(String apkOsPath, String resOsPath, String dexOsPath, String storeOsPath, PrintStream verboseStream)211     public ApkBuilder(String apkOsPath, String resOsPath, String dexOsPath, String storeOsPath,
212             PrintStream verboseStream) throws ApkCreationException {
213         this(new File(apkOsPath),
214              new File(resOsPath),
215              dexOsPath != null ? new File(dexOsPath) : null,
216              storeOsPath,
217              verboseStream);
218     }
219 
220     /**
221      * Creates a new instance.
222      *
223      * This creates a new builder that will create the specified output file, using the two
224      * mandatory given input files.
225      *
226      * An optional debug keystore can be provided. If set, it is expected that the store password
227      * is 'android' and the key alias and password are 'androiddebugkey' and 'android'.
228      *
229      * An optional {@link PrintStream} can also be provided for verbose output. If null, there will
230      * be no output.
231      *
232      * @param apkFile the file to create
233      * @param resFile the file representing the packaged resource file.
234      * @param dexFile the file representing the dex file. This can be null for apk with no code.
235      * @param storeOsPath the OS path to the debug keystore, if needed or null.
236      * @param verboseStream the stream to which verbose output should go. If null, verbose mode
237      *                      is not enabled.
238      * @throws ApkCreationException
239      */
ApkBuilder(File apkFile, File resFile, File dexFile, String storeOsPath, PrintStream verboseStream)240     public ApkBuilder(File apkFile, File resFile, File dexFile, String storeOsPath,
241             PrintStream verboseStream) throws ApkCreationException {
242         checkOutputFile(mApkFile = apkFile);
243         checkInputFile(mResFile = resFile, true /*throwIfDoesntExist*/);
244         if (dexFile != null) {
245             checkInputFile(mDexFile = dexFile, true /*throwIfDoesntExist*/);
246         } else {
247             mDexFile = null;
248         }
249         mVerboseStream = verboseStream;
250 
251         try {
252             File storeFile = null;
253             if (storeOsPath != null) {
254                 storeFile = new File(storeOsPath);
255                 checkInputFile(storeFile, false /*throwIfDoesntExist*/);
256             }
257 
258             if (storeFile != null) {
259                 // get the debug key
260                 verbosePrintln("Using keystore: %s", storeOsPath);
261 
262                 IKeyGenOutput keygenOutput = null;
263                 if (mVerboseStream != null) {
264                     keygenOutput = new IKeyGenOutput() {
265                         public void out(String message) {
266                             mVerboseStream.println(message);
267                         }
268 
269                         public void err(String message) {
270                             mVerboseStream.println(message);
271                         }
272                     };
273                 }
274 
275                 DebugKeyProvider keyProvider = new DebugKeyProvider(
276                         storeOsPath, null /*store type*/, keygenOutput);
277 
278                 PrivateKey key = keyProvider.getDebugKey();
279                 X509Certificate certificate = (X509Certificate)keyProvider.getCertificate();
280 
281                 if (key == null) {
282                     throw new ApkCreationException("Unable to get debug signature key");
283                 }
284 
285                 // compare the certificate expiration date
286                 if (certificate != null && certificate.getNotAfter().compareTo(new Date()) < 0) {
287                     // TODO, regenerate a new one.
288                     throw new ApkCreationException("Debug Certificate expired on " +
289                             DateFormat.getInstance().format(certificate.getNotAfter()));
290                 }
291 
292                 mBuilder = new SignedJarBuilder(
293                         new FileOutputStream(mApkFile, false /* append */), key,
294                         certificate);
295             } else {
296                 mBuilder = new SignedJarBuilder(
297                         new FileOutputStream(mApkFile, false /* append */),
298                         null /* key */, null /* certificate */);
299             }
300 
301             verbosePrintln("Packaging %s", mApkFile.getName());
302 
303             // add the resources
304             addZipFile(mResFile);
305 
306             // add the class dex file at the root of the apk
307             if (mDexFile != null) {
308                 addFile(mDexFile, SdkConstants.FN_APK_CLASSES_DEX);
309             }
310 
311         } catch (KeytoolException e) {
312             if (e.getJavaHome() == null) {
313                 throw new ApkCreationException(e.getMessage() +
314                         "\nJAVA_HOME seems undefined, setting it will help locating keytool automatically\n" +
315                         "You can also manually execute the following command\n:" +
316                         e.getCommandLine());
317             } else {
318                 throw new ApkCreationException(e.getMessage() +
319                         "\nJAVA_HOME is set to: " + e.getJavaHome() +
320                         "\nUpdate it if necessary, or manually execute the following command:\n" +
321                         e.getCommandLine());
322             }
323         } catch (Exception e) {
324             if (e instanceof ApkCreationException) {
325                 throw (ApkCreationException)e;
326             }
327 
328             throw new ApkCreationException(e);
329         }
330     }
331 
332     /**
333      * Sets the debug mode. In debug mode, when native libraries are present, the packaging
334      * will also include one or more copies of gdbserver in the final APK file.
335      *
336      * These are used for debugging native code, to ensure that gdbserver is accessible to the
337      * application.
338      *
339      * There will be one version of gdbserver for each ABI supported by the application.
340      *
341      * the gbdserver files are placed in the libs/abi/ folders automatically by the NDK.
342      *
343      * @param debugMode the debug mode flag.
344      */
setDebugMode(boolean debugMode)345     public void setDebugMode(boolean debugMode) {
346         mDebugMode = debugMode;
347     }
348 
349     /**
350      * Adds a file to the APK at a given path
351      * @param file the file to add
352      * @param archivePath the path of the file inside the APK archive.
353      * @throws ApkCreationException if an error occurred
354      * @throws SealedApkException if the APK is already sealed.
355      * @throws DuplicateFileException if a file conflicts with another already added to the APK
356      *                                   at the same location inside the APK archive.
357      */
addFile(File file, String archivePath)358     public void addFile(File file, String archivePath) throws ApkCreationException,
359             SealedApkException, DuplicateFileException {
360         if (mIsSealed) {
361             throw new SealedApkException("APK is already sealed");
362         }
363 
364         try {
365             doAddFile(file, archivePath);
366         } catch (DuplicateFileException e) {
367             throw e;
368         } catch (Exception e) {
369             throw new ApkCreationException(e, "Failed to add %s", file);
370         }
371     }
372 
373     /**
374      * Adds the content from a zip file.
375      * All file keep the same path inside the archive.
376      * @param zipFile the zip File.
377      * @throws ApkCreationException if an error occurred
378      * @throws SealedApkException if the APK is already sealed.
379      * @throws DuplicateFileException if a file conflicts with another already added to the APK
380      *                                   at the same location inside the APK archive.
381      */
addZipFile(File zipFile)382     public void addZipFile(File zipFile) throws ApkCreationException, SealedApkException,
383             DuplicateFileException {
384         if (mIsSealed) {
385             throw new SealedApkException("APK is already sealed");
386         }
387 
388         try {
389             verbosePrintln("%s:", zipFile);
390 
391             // reset the filter with this input.
392             mNullFilter.reset(zipFile);
393 
394             // ask the builder to add the content of the file.
395             FileInputStream fis = new FileInputStream(zipFile);
396             mBuilder.writeZip(fis, mNullFilter);
397         } catch (DuplicateFileException e) {
398             throw e;
399         } catch (Exception e) {
400             throw new ApkCreationException(e, "Failed to add %s", zipFile);
401         }
402     }
403 
404     /**
405      * Adds the resources from a jar file.
406      * @param jarFile the jar File.
407      * @return a {@link JarStatus} object indicating if native libraries where found in
408      *         the jar file.
409      * @throws ApkCreationException if an error occurred
410      * @throws SealedApkException if the APK is already sealed.
411      * @throws DuplicateFileException if a file conflicts with another already added to the APK
412      *                                   at the same location inside the APK archive.
413      */
addResourcesFromJar(File jarFile)414     public JarStatus addResourcesFromJar(File jarFile) throws ApkCreationException,
415             SealedApkException, DuplicateFileException {
416         if (mIsSealed) {
417             throw new SealedApkException("APK is already sealed");
418         }
419 
420         try {
421             verbosePrintln("%s:", jarFile);
422 
423             // reset the filter with this input.
424             mFilter.reset(jarFile);
425 
426             // ask the builder to add the content of the file, filtered to only let through
427             // the java resources.
428             FileInputStream fis = new FileInputStream(jarFile);
429             mBuilder.writeZip(fis, mFilter);
430 
431             // check if native libraries were found in the external library. This should
432             // constitutes an error or warning depending on if they are in lib/
433             return new JarStatusImpl(mFilter.getNativeLibs(), mFilter.getNativeLibsConflict());
434         } catch (DuplicateFileException e) {
435             throw e;
436         } catch (Exception e) {
437             throw new ApkCreationException(e, "Failed to add %s", jarFile);
438         }
439     }
440 
441     /**
442      * Adds the resources from a source folder.
443      * @param sourceFolder the source folder.
444      * @throws ApkCreationException if an error occurred
445      * @throws SealedApkException if the APK is already sealed.
446      * @throws DuplicateFileException if a file conflicts with another already added to the APK
447      *                                   at the same location inside the APK archive.
448      */
addSourceFolder(File sourceFolder)449     public void addSourceFolder(File sourceFolder) throws ApkCreationException, SealedApkException,
450             DuplicateFileException {
451         if (mIsSealed) {
452             throw new SealedApkException("APK is already sealed");
453         }
454 
455         if (sourceFolder.isDirectory()) {
456             try {
457                 // file is a directory, process its content.
458                 File[] files = sourceFolder.listFiles();
459                 for (File file : files) {
460                     processFileForResource(file, null);
461                 }
462             } catch (DuplicateFileException e) {
463                 throw e;
464             } catch (Exception e) {
465                 throw new ApkCreationException(e, "Failed to add %s", sourceFolder);
466             }
467         } else {
468             // not a directory? check if it's a file or doesn't exist
469             if (sourceFolder.exists()) {
470                 throw new ApkCreationException("%s is not a folder", sourceFolder);
471             } else {
472                 throw new ApkCreationException("%s does not exist", sourceFolder);
473             }
474         }
475     }
476 
477     /**
478      * Adds the native libraries from the top native folder.
479      * The content of this folder must be the various ABI folders.
480      *
481      * This may or may not copy gdbserver into the apk based on whether the debug mode is set.
482      *
483      * @param nativeFolder the native folder.
484      * @param abiFilter an optional filter. If not null, then only the matching ABI is included in
485      * the final archive
486      * @throws ApkCreationException if an error occurred
487      * @throws SealedApkException if the APK is already sealed.
488      * @throws DuplicateFileException if a file conflicts with another already added to the APK
489      *                                   at the same location inside the APK archive.
490      *
491      * @see #setDebugMode(boolean)
492      */
addNativeLibraries(File nativeFolder, String abiFilter)493     public void addNativeLibraries(File nativeFolder, String abiFilter)
494             throws ApkCreationException, SealedApkException, DuplicateFileException {
495         if (mIsSealed) {
496             throw new SealedApkException("APK is already sealed");
497         }
498 
499         if (nativeFolder.isDirectory() == false) {
500             // not a directory? check if it's a file or doesn't exist
501             if (nativeFolder.exists()) {
502                 throw new ApkCreationException("%s is not a folder", nativeFolder);
503             } else {
504                 throw new ApkCreationException("%s does not exist", nativeFolder);
505             }
506         }
507 
508         File[] abiList = nativeFolder.listFiles();
509 
510         if (abiFilter != null) {
511             verbosePrintln("Native folder: %1$s with filter %2$ss", nativeFolder, abiFilter);
512         } else {
513             verbosePrintln("Native folder: %s", nativeFolder);
514         }
515 
516         if (abiList != null) {
517             for (File abi : abiList) {
518                 if (abi.isDirectory()) { // ignore files
519 
520                     // check the abi filter and reject all other ABIs
521                     if (abiFilter != null && abiFilter.equals(abi.getName()) == false) {
522                         continue;
523                     }
524 
525                     File[] libs = abi.listFiles();
526                     if (libs != null) {
527                         for (File lib : libs) {
528                             // only consider files that are .so or, if in debug mode, that
529                             // are gdbserver executables
530                             if (lib.isFile() &&
531                                     (PATTERN_NATIVELIB_EXT.matcher(lib.getName()).matches() ||
532                                             (mDebugMode &&
533                                                     SdkConstants.FN_GDBSERVER.equals(
534                                                             lib.getName())))) {
535                                 String path =
536                                     SdkConstants.FD_APK_NATIVE_LIBS + "/" +
537                                     abi.getName() + "/" + lib.getName();
538 
539                                 try {
540                                     doAddFile(lib, path);
541                                 } catch (IOException e) {
542                                     throw new ApkCreationException(e, "Failed to add %s", lib);
543                                 }
544                             }
545                         }
546                     }
547                 }
548             }
549         }
550     }
551 
552     /**
553      * Seals the APK, and signs it if necessary.
554      * @throws ApkCreationException
555      * @throws ApkCreationException if an error occurred
556      * @throws SealedApkException if the APK is already sealed.
557      */
sealApk()558     public void sealApk() throws ApkCreationException, SealedApkException {
559         if (mIsSealed) {
560             throw new SealedApkException("APK is already sealed");
561         }
562 
563         // close and sign the application package.
564         try {
565             mBuilder.close();
566             mIsSealed = true;
567         } catch (Exception e) {
568             throw new ApkCreationException(e, "Failed to seal APK");
569         }
570     }
571 
572     /**
573      * Output a given message if the verbose mode is enabled.
574      * @param format the format string for {@link String#format(String, Object...)}
575      * @param args the string arguments
576      */
verbosePrintln(String format, Object... args)577     private void verbosePrintln(String format, Object... args) {
578         if (mVerboseStream != null) {
579             mVerboseStream.println(String.format(format, args));
580         }
581     }
582 
doAddFile(File file, String archivePath)583     private void doAddFile(File file, String archivePath) throws DuplicateFileException,
584             IOException {
585         verbosePrintln("%1$s => %2$s", file, archivePath);
586 
587         File duplicate = checkFileForDuplicate(archivePath);
588         if (duplicate != null) {
589             throw new DuplicateFileException(archivePath, duplicate, file);
590         }
591 
592         mAddedFiles.put(archivePath, file);
593         mBuilder.writeFile(file, archivePath);
594     }
595 
596     /**
597      * Processes a {@link File} that could be a {@link ApkFile}, or a folder containing
598      * java resources.
599      * @param file the {@link File} to process.
600      * @param path the relative path of this file to the source folder. Can be <code>null</code> to
601      * identify a root file.
602      * @throws IOException
603      * @throws DuplicateFileException if a file conflicts with another already added to the APK
604      *                                   at the same location inside the APK archive.
605      */
processFileForResource(File file, String path)606     private void processFileForResource(File file, String path)
607             throws IOException, DuplicateFileException {
608         if (file.isDirectory()) {
609             // a directory? we check it
610             if (checkFolderForPackaging(file.getName())) {
611                 // if it's valid, we append its name to the current path.
612                 if (path == null) {
613                     path = file.getName();
614                 } else {
615                     path = path + "/" + file.getName();
616                 }
617 
618                 // and process its content.
619                 File[] files = file.listFiles();
620                 for (File contentFile : files) {
621                     processFileForResource(contentFile, path);
622                 }
623             }
624         } else {
625             // a file? we check it to make sure it should be added
626             if (checkFileForPackaging(file.getName())) {
627                 // we append its name to the current path
628                 if (path == null) {
629                     path = file.getName();
630                 } else {
631                     path = path + "/" + file.getName();
632                 }
633 
634                 // and add it to the apk
635                 doAddFile(file, path);
636             }
637         }
638     }
639 
640     /**
641      * Checks if the given path in the APK archive has not already been used and if it has been,
642      * then returns a {@link File} object for the source of the duplicate
643      * @param archivePath the archive path to test.
644      * @return A File object of either a file at the same location or an archive that contains a
645      * file that was put at the same location.
646      */
checkFileForDuplicate(String archivePath)647     private File checkFileForDuplicate(String archivePath) {
648         return mAddedFiles.get(archivePath);
649     }
650 
651     /**
652      * Checks an output {@link File} object.
653      * This checks the following:
654      * - the file is not an existing directory.
655      * - if the file exists, that it can be modified.
656      * - if it doesn't exists, that a new file can be created.
657      * @param file the File to check
658      * @throws ApkCreationException If the check fails
659      */
checkOutputFile(File file)660     private void checkOutputFile(File file) throws ApkCreationException {
661         if (file.isDirectory()) {
662             throw new ApkCreationException("%s is a directory!", file);
663         }
664 
665         if (file.exists()) { // will be a file in this case.
666             if (file.canWrite() == false) {
667                 throw new ApkCreationException("Cannot write %s", file);
668             }
669         } else {
670             try {
671                 if (file.createNewFile() == false) {
672                     throw new ApkCreationException("Failed to create %s", file);
673                 }
674             } catch (IOException e) {
675                 throw new ApkCreationException(
676                         "Failed to create '%1$ss': %2$s", file, e.getMessage());
677             }
678         }
679     }
680 
681     /**
682      * Checks an input {@link File} object.
683      * This checks the following:
684      * - the file is not an existing directory.
685      * - that the file exists (if <var>throwIfDoesntExist</var> is <code>false</code>) and can
686      *    be read.
687      * @param file the File to check
688      * @param indicates whether the method should throw {@link ApkCreationException} if the file
689      *        does not exist at all.
690      * @throws ApkCreationException If the check fails
691      */
checkInputFile(File file, boolean throwIfDoesntExist)692     private void checkInputFile(File file, boolean throwIfDoesntExist) throws ApkCreationException {
693         if (file.isDirectory()) {
694             throw new ApkCreationException("%s is a directory!", file);
695         }
696 
697         if (file.exists()) {
698             if (file.canRead() == false) {
699                 throw new ApkCreationException("Cannot read %s", file);
700             }
701         } else if (throwIfDoesntExist) {
702             throw new ApkCreationException("%s does not exist", file);
703         }
704     }
705 
getDebugKeystore()706     public static String getDebugKeystore() throws ApkCreationException {
707         try {
708             return DebugKeyProvider.getDefaultKeyStoreOsPath();
709         } catch (Exception e) {
710             throw new ApkCreationException(e, e.getMessage());
711         }
712     }
713 
714     /**
715      * Checks whether a folder and its content is valid for packaging into the .apk as
716      * standard Java resource.
717      * @param folderName the name of the folder.
718      */
checkFolderForPackaging(String folderName)719     public static boolean checkFolderForPackaging(String folderName) {
720         return folderName.equalsIgnoreCase("CVS") == false &&
721             folderName.equalsIgnoreCase(".svn") == false &&
722             folderName.equalsIgnoreCase("SCCS") == false &&
723             folderName.equalsIgnoreCase("META-INF") == false &&
724             folderName.startsWith("_") == false;
725     }
726 
727     /**
728      * Checks a file to make sure it should be packaged as standard resources.
729      * @param fileName the name of the file (including extension)
730      * @return true if the file should be packaged as standard java resources.
731      */
checkFileForPackaging(String fileName)732     public static boolean checkFileForPackaging(String fileName) {
733         String[] fileSegments = fileName.split("\\.");
734         String fileExt = "";
735         if (fileSegments.length > 1) {
736             fileExt = fileSegments[fileSegments.length-1];
737         }
738 
739         return checkFileForPackaging(fileName, fileExt);
740     }
741 
742     /**
743      * Checks a file to make sure it should be packaged as standard resources.
744      * @param fileName the name of the file (including extension)
745      * @param extension the extension of the file (excluding '.')
746      * @return true if the file should be packaged as standard java resources.
747      */
checkFileForPackaging(String fileName, String extension)748     public static boolean checkFileForPackaging(String fileName, String extension) {
749         // Note: this method is used by com.android.ide.eclipse.adt.internal.build.ApkBuilder
750         if (fileName.charAt(0) == '.') { // ignore hidden files.
751             return false;
752         }
753 
754         return "aidl".equalsIgnoreCase(extension) == false &&       // Aidl files
755             "java".equalsIgnoreCase(extension) == false &&          // Java files
756             "class".equalsIgnoreCase(extension) == false &&         // Java class files
757             "scc".equalsIgnoreCase(extension) == false &&           // VisualSourceSafe
758             "swp".equalsIgnoreCase(extension) == false &&           // vi swap file
759             "package.html".equalsIgnoreCase(fileName) == false &&   // Javadoc
760             "overview.html".equalsIgnoreCase(fileName) == false &&  // Javadoc
761             ".cvsignore".equalsIgnoreCase(fileName) == false &&     // CVS
762             ".DS_Store".equals(fileName) == false &&                // Mac resources
763             fileName.charAt(fileName.length()-1) != '~';            // Backup files
764     }
765 }
766