Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  /*
   * Copyright (C) 2010 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 package com.android.builder.internal.packaging;
 
 
 import java.io.File;
 import java.net.URL;
 import java.util.List;
 import java.util.Set;
Class making the final app package. The inputs are: - packaged resources (output of aapt) - code file (ouput of dx) - Java resources coming from the project, its libraries, and its jar files - Native libraries from the project or its library.
 
 public final class Packager implements IArchiveBuilder {
 
     private static final Pattern PATTERN_NATIVELIB_EXT = Pattern.compile("^.+\\.so$",
             .);

    
A No-op zip filter. It's used to detect conflicts.
 
     private final class NullZipFilter implements IZipEntryFilter {
         private File mInputFile;
 
         void reset(File inputFile) {
              = inputFile;
         }
 
         @Override
         public boolean checkEntry(String archivePaththrows ZipAbortException {
             .verbose("=> %s"archivePath);
 
             File duplicate = checkFileForDuplicate(archivePath);
             if (duplicate != null) {
                 throw new DuplicateFileException(archivePathduplicate);
             } else {
                 .put(archivePath);
             }
 
             return true;
         }
     }

    
Custom com.android.builder.signing.SignedJarBuilder.IZipEntryFilter to filter out everything that is not a standard java resources, and also record whether the zip file contains native libraries.

Used in com.android.builder.signing.SignedJarBuilder.writeZip(java.io.InputStream,com.android.builder.signing.SignedJarBuilder.IZipEntryFilter) when we only want the java resources from external jars.

 
     private final class JavaAndNativeResourceFilter implements IZipEntryFilter {
         private final List<StringmNativeLibs = new ArrayList<String>();
        private Set<StringmUsedPickFirsts = null;
        @Nullable
        private final PackagingOptions mPackagingOptions;
        @NonNull
        private final Set<StringmExcludes;
        @NonNull
        private final Set<StringmPickFirsts;
        private boolean mNativeLibsConflict = false;
        private File mInputFile;
        private JavaAndNativeResourceFilter(@Nullable PackagingOptions packagingOptions) {
             = packagingOptions;
             =  != null ? .getExcludes() :
                    Collections.<String>emptySet();
             =  != null ? .getPickFirsts() :
                    Collections.<String>emptySet();
        }
        @Override
        public boolean checkEntry(String archivePaththrows ZipAbortException {
            // split the path into segments.
            String[] segments = archivePath.split("/");
            // empty path? skip to next entry.
            if (segments.length == 0) {
                return false;
            }
            //noinspection VariableNotUsedInsideIf
            if ( != null) {
                if (.contains(archivePath)) {
                    return false;
                }
                if (.contains(archivePath)) {
                    if ( == null) {
                         = Sets.newHashSetWithExpectedSize(.size());
                    }
                    if (.contains(archivePath)) {
                        return false;
                    } else {
                        .add(archivePath);
                    }
                }
            }
            // Check each folders to make sure they should be included.
            // Folders like CVS, .svn, etc.. should already have been excluded from the
            // jar file, but we need to exclude some other folder (like /META-INF) so
            // we check anyway.
            for (int i = 0 ; i < segments.length - 1; i++) {
                if (!PackagingUtils.checkFolderForPackaging(segments[i])) {
                    return false;
                }
            }
            // get the file name from the path
            String fileName = segments[segments.length-1];
            boolean check = PackagingUtils.checkFileForPackaging(fileName);
            // only do additional checks if the file passes the default checks.
            if (check) {
                .verbose("=> %s"archivePath);
                File duplicate = checkFileForDuplicate(archivePath);
                if (duplicate != null) {
                    throw new DuplicateFileException(archivePathduplicate);
                } else {
                    .put(archivePath);
                }
                if (archivePath.endsWith(".so")) {
                    .add(archivePath);
                    // only .so located in lib/ will interfere with the installation
                    if (archivePath.startsWith(. + "/")) {
                         = true;
                    }
                } else if (archivePath.endsWith(".jnilib")) {
                    .add(archivePath);
                }
            }
            return check;
        }
        List<StringgetNativeLibs() {
            return ;
        }
        boolean getNativeLibsConflict() {
            return ;
        }
        void reset(File inputFile) {
             = inputFile;
            .clear();
             = false;
        }
    }
    private SignedJarBuilder mBuilder = null;
    private final ILogger mLogger;
    private boolean mJniDebugMode = false;
    private boolean mIsSealed = false;
    private final NullZipFilter mNullFilter = new NullZipFilter();
    private final JavaAndNativeResourceFilter mFilter;
    private final HashMap<StringFilemAddedFiles = new HashMap<StringFile>();

    
Status for the addition of a jar file resources into the APK. This indicates possible issues with native library inside the jar file.
    public interface JarStatus {
        
Returns the list of native libraries found in the jar file.
        List<StringgetNativeLibs();

        
Returns whether some of those libraries were located in the location that Android expects its native libraries.
        boolean hasNativeLibsConflicts();
    }

    
Internal implementation of Packager.JarStatus.
    private static final class JarStatusImpl implements JarStatus {
        public final List<StringmLibs;
        public final boolean mNativeLibsConflict;
        private JarStatusImpl(List<Stringlibsboolean nativeLibsConflict) {
             = libs;
             = nativeLibsConflict;
        }
        @Override
        public List<StringgetNativeLibs() {
            return ;
        }
        @Override
        public boolean hasNativeLibsConflicts() {
            return ;
        }
    }

    
Creates a new instance. This creates a new builder that will create the specified output file, using the two mandatory given input files. An optional debug keystore can be provided. If set, it is expected that the store password is 'android' and the key alias and password are 'androiddebugkey' and 'android'. An optional com.android.utils.ILogger can also be provided for verbose output. If null, there will be no output.

Parameters:
apkLocation the file to create
resLocation the file representing the packaged resource file.
dexFolder the folder containing the dex file.
certificateInfo the signing information used to sign the package. Optional the OS path to the debug keystore, if needed or null.
logger the logger.
Throws:
com.android.builder.packaging.PackagerException
    public Packager(
            @NonNull String apkLocation,
            @NonNull String resLocation,
            @NonNull File dexFolder,
            CertificateInfo certificateInfo,
            @Nullable String createdBy,
            @Nullable PackagingOptions packagingOptions,
            ILogger loggerthrows PackagerException {
         = new JavaAndNativeResourceFilter(packagingOptions);
        try {
            File apkFile = new File(apkLocation);
            checkOutputFile(apkFile);
            File resFile = new File(resLocation);
            checkInputFile(resFile);
             = logger;
             = new SignedJarBuilder(
                    new FileOutputStream(apkFilefalse /* append */),
                    certificateInfo != null ? certificateInfo.getKey() : null,
                    certificateInfo != null ? certificateInfo.getCertificate() : null,
                    getLocalVersion(),
                    createdBy);
            .verbose("Packaging %s"apkFile.getName());
            // add the resources
            addZipFile(resFile);
            // add the class dex file at the root of the apk
            addDexFolder(dexFolder);
        } catch (PackagerException e) {
            if ( != null) {
                .cleanUp();
            }
            throw e;
        } catch (Exception e) {
            if ( != null) {
                .cleanUp();
            }
            throw new PackagerException(e);
        }
    }
    private void addDexFolder(@NonNull File dexFolder)
        File[] files = dexFolder.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File fileString name) {
                return name.endsWith(.);
            }
        });
        if (files != null && files.length > 0) {
            for (File file : files) {
                addFile(filefile.getName());
            }
        }
    }

    
Sets the JNI debug mode. In debug mode, when native libraries are present, the packaging will also include one or more copies of gdbserver in the final APK file. These are used for debugging native code, to ensure that gdbserver is accessible to the application. There will be one version of gdbserver for each ABI supported by the application. the gbdserver files are placed in the libs/abi/ folders automatically by the NDK.

Parameters:
jniDebugMode the jni-debug mode flag.
    public void setJniDebugMode(boolean jniDebugMode) {
         = jniDebugMode;
    }

    
Adds a file to the APK at a given path

Parameters:
file the file to add
archivePath the path of the file inside the APK archive.
Throws:
com.android.builder.packaging.PackagerException if an error occurred
com.android.builder.packaging.SealedPackageException if the APK is already sealed.
com.android.builder.packaging.DuplicateFileException if a file conflicts with another already added to the APK at the same location inside the APK archive.
    @Override
    public void addFile(File fileString archivePaththrows PackagerException,
        if () {
            throw new SealedPackageException("APK is already sealed");
        }
        try {
            doAddFile(filearchivePath);
        } catch (DuplicateFileException e) {
            .cleanUp();
            throw e;
        } catch (Exception e) {
            .cleanUp();
            throw new PackagerException(e"Failed to add %s"file);
        }
    }

    
Adds the content from a zip file. All file keep the same path inside the archive.

Parameters:
zipFile the zip File.
Throws:
com.android.builder.packaging.PackagerException if an error occurred
com.android.builder.packaging.SealedPackageException if the APK is already sealed.
com.android.builder.packaging.DuplicateFileException if a file conflicts with another already added to the APK at the same location inside the APK archive.
    void addZipFile(File zipFilethrows PackagerExceptionSealedPackageException,
            DuplicateFileException {
        if () {
            throw new SealedPackageException("APK is already sealed");
        }
        FileInputStream fis = null;
        try {
            .verbose("%s:"zipFile);
            // reset the filter with this input.
            .reset(zipFile);
            // ask the builder to add the content of the file.
            fis = new FileInputStream(zipFile);
            .writeZip(fis);
        } catch (DuplicateFileException e) {
            .cleanUp();
            throw e;
        } catch (Exception e) {
            .cleanUp();
            throw new PackagerException(e"Failed to add %s"zipFile);
        } finally {
            try {
                Closeables.close(fistrue /* swallowIOException */);
            } catch (IOException e) {
                // ignore
            }
        }
    }

    
Adds the resources from a jar file.

Parameters:
jarFile the jar File.
Returns:
a Packager.JarStatus object indicating if native libraries where found in the jar file.
Throws:
com.android.builder.packaging.PackagerException if an error occurred
com.android.builder.packaging.SealedPackageException if the APK is already sealed.
com.android.builder.packaging.DuplicateFileException if a file conflicts with another already added to the APK at the same location inside the APK archive.
    public JarStatus addResourcesFromJar(File jarFilethrows PackagerException,
        if () {
            throw new SealedPackageException("APK is already sealed");
        }
        FileInputStream fis = null;
        try {
            .verbose("%s:"jarFile);
            // reset the filter with this input.
            .reset(jarFile);
            // ask the builder to add the content of the file, filtered to only let through
            // the java resources.
            fis = new FileInputStream(jarFile);
            .writeZip(fis);
            // check if native libraries were found in the external library. This should
            // constitutes an error or warning depending on if they are in lib/
            return new JarStatusImpl(.getNativeLibs(), .getNativeLibsConflict());
        } catch (DuplicateFileException e) {
            .cleanUp();
            throw e;
        } catch (Exception e) {
            .cleanUp();
            throw new PackagerException(e"Failed to add %s"jarFile);
        } finally {
            try {
                Closeables.close(fistrue /* swallowIOException */);
            } catch (IOException e) {
                // ignore.
            }
        }
    }

    
Adds the native libraries from the top native folder. The content of this folder must be the various ABI folders. This may or may not copy gdbserver into the apk based on whether the debug mode is set.

Parameters:
nativeFolder the root folder containing the abi folders which contain the .so
Throws:
com.android.builder.packaging.PackagerException if an error occurred
com.android.builder.packaging.SealedPackageException if the APK is already sealed.
com.android.builder.packaging.DuplicateFileException if a file conflicts with another already added to the APK at the same location inside the APK archive.
See also:
setJniDebugMode(boolean)
    public void addNativeLibraries(@NonNull File nativeFolder, @Nullable Set<StringabiFilters)
        if () {
            throw new SealedPackageException("APK is already sealed");
        }
        if (!nativeFolder.isDirectory()) {
            // not a directory? check if it's a file or doesn't exist
            if (nativeFolder.exists()) {
                throw new PackagerException("%s is not a folder"nativeFolder);
            } else {
                throw new PackagerException("%s does not exist"nativeFolder);
            }
        }
        File[] abiList = nativeFolder.listFiles();
        .verbose("Native folder: %s"nativeFolder);
        if (abiList != null) {
            for (File abi : abiList) {
                if (abiFilters != null && !abiFilters.contains(abi.getName())) {
                    continue;
                }
                if (abi.isDirectory()) { // ignore files
                    File[] libs = abi.listFiles();
                    if (libs != null) {
                        for (File lib : libs) {
                            // only consider files that are .so or, if in debug mode, that
                            // are gdbserver executables
                            String libName = lib.getName();
                            if (lib.isFile() &&
                                    (.matcher(lib.getName()).matches() ||
                                        ( &&
                                            (..equals(libName) ||
                                             ..equals(libName))))) {
                                String path =
                                    . + "/" +
                                    abi.getName() + "/" + libName;
                                try {
                                    doAddFile(libpath);
                                } catch (IOException e) {
                                    .cleanUp();
                                    throw new PackagerException(e"Failed to add %s"lib);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    
Seals the APK, and signs it if necessary.

    public void sealApk() throws PackagerExceptionSealedPackageException {
        if () {
            throw new SealedPackageException("APK is already sealed");
        }
        // close and sign the application package.
        try {
            .close();
             = true;
        } catch (Exception e) {
            throw new PackagerException(e"Failed to seal APK");
        } finally {
            .cleanUp();
        }
    }
    private void doAddFile(File fileString archivePaththrows DuplicateFileException,
            IOException {
        .verbose("%1$s => %2$s"filearchivePath);
        File duplicate = checkFileForDuplicate(archivePath);
        if (duplicate != null) {
            throw new DuplicateFileException(archivePathduplicatefile);
        }
        .put(archivePathfile);
        .writeFile(filearchivePath);
    }

    
Checks if the given path in the APK archive has not already been used and if it has been, then returns a java.io.File object for the source of the duplicate

Parameters:
archivePath the archive path to test.
Returns:
A File object of either a file at the same location or an archive that contains a file that was put at the same location.
    private File checkFileForDuplicate(String archivePath) {
        return .get(archivePath);
    }

    
Checks an output java.io.File object. This checks the following: - the file is not an existing directory. - if the file exists, that it can be modified. - if it doesn't exists, that a new file can be created.

Parameters:
file the File to check
Throws:
com.android.builder.packaging.PackagerException If the check fails
    private void checkOutputFile(File filethrows PackagerException {
        if (file.isDirectory()) {
            throw new PackagerException("%s is a directory!"file);
        }
        if (file.exists()) { // will be a file in this case.
            if (!file.canWrite()) {
                throw new PackagerException("Cannot write %s"file);
            }
        } else {
            try {
                if (!file.createNewFile()) {
                    throw new PackagerException("Failed to create %s"file);
                }
            } catch (IOException e) {
                throw new PackagerException(
                        "Failed to create '%1$ss': %2$s"filee.getMessage());
            }
        }
    }

    
Checks an input java.io.File object. This checks the following: - the file is not an existing directory. - that the file exists (if throwIfDoesntExist is false) and can be read.

Parameters:
file the File to check
Throws:
java.io.FileNotFoundException if the file is not here.
com.android.builder.packaging.PackagerException If the file is a folder or a file that cannot be read.
    private static void checkInputFile(File filethrows FileNotFoundExceptionPackagerException {
        if (file.isDirectory()) {
            throw new PackagerException("%s is a directory!"file);
        }
        if (file.exists()) {
            if (!file.canRead()) {
                throw new PackagerException("Cannot read %s"file);
            }
        } else {
            throw new FileNotFoundException(String.format("%s does not exist"file));
        }
    }
    private static String getLocalVersion() {
        Class clazz = Packager.class;
        String className = clazz.getSimpleName() + ".class";
        String classPath = clazz.getResource(className).toString();
        if (!classPath.startsWith("jar")) {
            // Class not from JAR, unlikely
            return null;
        }
        try {
            String manifestPath = classPath.substring(0, classPath.lastIndexOf('!') + 1) +
                    "/META-INF/MANIFEST.MF";
            Manifest manifest = new Manifest(new URL(manifestPath).openStream());
            Attributes attr = manifest.getMainAttributes();
            return attr.getValue("Builder-Version");
        } catch (MalformedURLException ignored) {
        } catch (IOException ignored) {
        }
        return null;
    }
New to GrepCode? Check out our FAQ X