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