diff options
Diffstat (limited to 'src/plugins/certmanager/src/com/motorolamobility/studio/android/certmanager/packaging/PackageFile.java')
-rw-r--r-- | src/plugins/certmanager/src/com/motorolamobility/studio/android/certmanager/packaging/PackageFile.java | 648 |
1 files changed, 648 insertions, 0 deletions
diff --git a/src/plugins/certmanager/src/com/motorolamobility/studio/android/certmanager/packaging/PackageFile.java b/src/plugins/certmanager/src/com/motorolamobility/studio/android/certmanager/packaging/PackageFile.java new file mode 100644 index 0000000..27a565d --- /dev/null +++ b/src/plugins/certmanager/src/com/motorolamobility/studio/android/certmanager/packaging/PackageFile.java @@ -0,0 +1,648 @@ +/* + * Copyright (C) 2012 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.motorolamobility.studio.android.certmanager.packaging; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.WritableByteChannel; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.CRC32; + +import org.eclipse.core.runtime.IBundleGroup; +import org.eclipse.core.runtime.IBundleGroupProvider; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Platform; + +import com.motorola.studio.android.common.IAndroidConstants; +import com.motorola.studio.android.common.log.StudioLogger; +import com.motorola.studio.android.common.utilities.AndroidUtils; +import com.motorola.studio.android.common.utilities.FileUtil; +import com.motorolamobility.studio.android.certmanager.CertificateManagerActivator; + +/** + * This class is an in-memory package file representation. + */ +public class PackageFile +{ + private static final String COM_MOTOROLA_STUDIO_ANDROID_FEATURE = + "com.motorola.studio.android.feature"; + + /* + * Map of entries contained in this package file + */ + private final Map<String, File> entryMap = new HashMap<String, File>(); + + /* + * Map of temporary entries contained in this package file (it duplicates + * the entries on entryMap) + */ + private final Map<String, File> tempEntryMap = new HashMap<String, File>(); + + /* + * Package manifest + */ + private Manifest manifest; + + private int apiVersion; + + private String targetName; + + private Set<String> rawFiles = new HashSet<String>(); + + /** + * Creates an empty PackageFile. + * + * @param createdBy + * Created-By manifest attribute + */ + public PackageFile(String createdBy) + { + targetName = ""; + apiVersion = -1; + manifest = createManifest(createdBy); + } + + /** + * Creates an empty PackageFile + * + * @param createdBy + * Created-By manifest attribute + */ + public PackageFile(String createdBy, String targetName, int apiVersion) + { + this.targetName = targetName; + this.apiVersion = apiVersion; + manifest = createManifest(createdBy); + } + + /** + * Creates a PackageFile from an existing JarFile + * + * @param jarFile + * the base jar file + * @param apiVersion + * @param targetName + * @throws IOException + * if an I/O error occurs when reading the contents of the base + * jar file + */ + public PackageFile(JarFile jarFile) throws IOException + { + this(jarFile, "", -1); + } + + /** + * Creates a PackageFile from an existing JarFile + * + * @param jarFile + * the base jar file + * @param apiVersion + * @param targetName + * @throws IOException + * if an I/O error occurs when reading the contents of the base + * jar file + */ + public PackageFile(JarFile jarFile, String targetName, int apiVersion) throws IOException + { + manifest = jarFile.getManifest(); + this.targetName = targetName; + this.apiVersion = apiVersion; + String createdBy = generateStudioFingerprint(); + if (manifest == null) + { + manifest = createManifest(createdBy); + } + + // go through all the entries in the base jar file + Enumeration<JarEntry> entryEnum = jarFile.entries(); + + while (entryEnum.hasMoreElements()) + { + JarEntry entry = entryEnum.nextElement(); + if (!entry.getName().equalsIgnoreCase( + CertificateManagerActivator.METAFILES_DIR + + CertificateManagerActivator.JAR_SEPARATOR + + CertificateManagerActivator.MANIFEST_FILE_NAME)) + { + // create a temporary file for this entry + InputStream is = jarFile.getInputStream(entry); + File tempFile = + File.createTempFile(CertificateManagerActivator.TEMP_FILE_PREFIX, null); + tempFile.deleteOnExit(); + + // copy contents from the original file to the temporary file + BufferedInputStream bis = null; + BufferedOutputStream bos = null; + + try + { + bis = new BufferedInputStream(is); + bos = new BufferedOutputStream(new FileOutputStream(tempFile)); + + int c; + while ((c = bis.read()) >= 0) + { + bos.write(c); + } + } + finally + { + if (bis != null) + { + bis.close(); + } + + if (bos != null) + { + bos.close(); + } + } + + // add the temporary file to the package file + setTempEntryFile(entry.getName(), tempFile); + + //check if the entry is not compressed to keep it this way + if (entry.getMethod() == JarEntry.STORED) + { + rawFiles.add(entry.getName()); + } + } + } + } + + private String generateStudioFingerprint() + { + IBundleGroupProvider[] providers = Platform.getBundleGroupProviders(); + List<IBundleGroup> groups = new ArrayList<IBundleGroup>(); + if (providers != null) + { + for (int i = 0; i < providers.length; ++i) + { + IBundleGroup[] bundleGroups = providers[i].getBundleGroups(); + groups.addAll(Arrays.asList(bundleGroups)); + } + } + String version = ""; + for (IBundleGroup group : groups) + { + if (group.getIdentifier().equals(COM_MOTOROLA_STUDIO_ANDROID_FEATURE)) + { + version = group.getVersion(); + break; + } + } + + StringBuilder stringBuilder = + new StringBuilder(CertificateManagerActivator.CREATED_BY_FIELD_VALUE); + stringBuilder.append(" v"); + stringBuilder.append(version); + stringBuilder.append(" - "); + stringBuilder.append(Platform.getOS()); + stringBuilder.append(", "); + stringBuilder.append(Platform.getOSArch()); + stringBuilder.append(". "); + if (targetName.trim().length() > 0) + { + stringBuilder.append("Android target - "); + stringBuilder.append(targetName); + stringBuilder.append(", "); + } + if (apiVersion >= 0) + { + stringBuilder.append("API version - "); + stringBuilder.append(apiVersion); + stringBuilder.append("."); + } + return stringBuilder.toString(); + } + + /** + * Gets the names for all the entries in this package file + * + * @return Set containing the names for all the entries in this package file + */ + public Set<String> getEntryNames() + { + return Collections.unmodifiableSet(entryMap.keySet()); + } + + /** + * Gets the File object for a given entry + * + * @param entryName + * the entry name + * @return the File object corresponding to entryName + */ + public File getEntryFile(String entryName) + { + return entryMap.get(entryName); + } + + /** + * Puts a File object as a named entry in this package file + * + * @param entryName + * the entry name + * @param file + * the File object corresponding to entryName + */ + public void setEntryFile(String entryName, File file) + { + entryMap.put(entryName, file); + } + + /** + * Puts a Temporary File object as a named entry in this package file + * + * @param entryName + * the entry name + * @param tempFile + * the temporary file object corresponding to entryName + */ + public void setTempEntryFile(String entryName, File tempFile) + { + entryMap.put(entryName, tempFile); + tempEntryMap.put(entryName, tempFile); + } + + /** + * Remove the named entry of files map of this package + * + * @param entryName + * the name of entry to be removed + * @throws IOException + */ + public void removeEntryFile(String entryName) throws IOException + { + File entryFile = entryMap.get(entryName); + if (entryFile != null) + { + entryMap.remove(entryName); + if (tempEntryMap.containsKey(entryName)) + { + tempEntryMap.remove(entryName); + deleteFile(entryFile); + } + } + } + + /** + * Remove the meta entry files (files under META-INF folder) + * + * @throws IOException + */ + public void removeMetaEntryFiles() throws IOException + { + String createdBy = + manifest.getMainAttributes().getValue(CertificateManagerActivator.CREATED_BY_FIELD); + Set<String> entries = new HashSet<String>(getEntryNames()); + for (String entry : entries) + { + if (entry.startsWith(CertificateManagerActivator.METAFILES_DIR)) + { + removeEntryFile(entry); + } + } + Manifest cleanManifest = new Manifest(); + cleanManifest.getMainAttributes().putAll(manifest.getMainAttributes()); + if ("".equals(targetName) && (apiVersion <= 0)) //Just removing signatures. + { + cleanManifest.getMainAttributes().putValue( + CertificateManagerActivator.CREATED_BY_FIELD, createdBy); + } + else + { + cleanManifest.getMainAttributes().putValue( + CertificateManagerActivator.CREATED_BY_FIELD, generateStudioFingerprint()); + } + manifest = cleanManifest; + } + + private void writeCompressed(JarOutputStream jarOut, String entryName) throws IOException + { + File file = entryMap.get(entryName); + if ((file.exists()) && (file.isFile())) + { + JarEntry entry = new JarEntry(entryName); + jarOut.putNextEntry(entry); + WritableByteChannel outputChannel = null; + FileChannel readFromFileChannel = null; + FileInputStream inputStream = null; + try + { + outputChannel = Channels.newChannel(jarOut); + inputStream = new FileInputStream(file); + readFromFileChannel = inputStream.getChannel(); + readFromFileChannel.transferTo(0, file.length(), outputChannel); + } + finally + { + try + { + if (jarOut != null) + { + jarOut.closeEntry(); + } + if (readFromFileChannel != null) + { + readFromFileChannel.close(); + } + if (inputStream != null) + { + inputStream.close(); + } + } + catch (IOException e) + { + StudioLogger.error("Could not close stream: ", e.getMessage()); //$NON-NLS-1$ + } + } + } + + } + + private void writeRaw(JarOutputStream jarOut, String entryName) throws IOException + { + FileInputStream inputStream = null; + File file = entryMap.get(entryName); + if ((file.exists()) && (file.isFile())) + { + CRC32 crc = new CRC32(); + JarEntry entry = new JarEntry(entryName); + entry.setMethod(JarEntry.STORED); + entry.setSize(file.length()); + WritableByteChannel outputChannel = null; + FileChannel readFromFileChannel = null; + try + { + outputChannel = Channels.newChannel(jarOut); + inputStream = new FileInputStream(file); + readFromFileChannel = inputStream.getChannel(); + + ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024); + crc.reset(); + while (readFromFileChannel.read(buffer) > 0) + { + buffer.flip(); + byte[] byteArray = new byte[buffer.limit()]; + buffer.get(byteArray, 0, buffer.limit()); + crc.update(byteArray); + buffer.clear(); + } + entry.setCrc(crc.getValue()); + jarOut.putNextEntry(entry); + readFromFileChannel.transferTo(0, file.length(), outputChannel); + } + finally + { + if (inputStream != null) + { + inputStream.close(); + } + if (readFromFileChannel != null) + { + readFromFileChannel.close(); + } + jarOut.closeEntry(); + } + } + } + + /** + * Writes this package file to an output stream + * + * @param outputStream + * the stream to write the package to + * @throws IOException + * if an I/O error occurs when writing the package contents to + * the output stream + */ + public void write(OutputStream outputStream) throws IOException + { + // create a jar output stream + JarOutputStream jarOut = null; + + try + { + jarOut = new JarOutputStream(outputStream, manifest); + + // for each entry in the package file + for (String jarEntryName : entryMap.keySet()) + { + if (jarEntryName.contains("raw/") || rawFiles.contains(jarEntryName)) + { + writeRaw(jarOut, jarEntryName); + } + else + { + writeCompressed(jarOut, jarEntryName); + } + } + } + finally + { + if (jarOut != null) + { + try + { + jarOut.finish(); + jarOut.close(); + } + catch (IOException e) + { + StudioLogger.error("Could not close stream while writing jar file. " + + e.getMessage()); + } + } + } + } + + /** + * Calculate the package total size returns long + */ + public long getTotalSize() + { + long totalSize = 0; + + for (String jarEntryName : entryMap.keySet()) + { + File file = entryMap.get(jarEntryName); + if ((file.exists()) && (file.isFile())) + { + totalSize += file.length(); + } + } + + return totalSize; + } + + /** + * Gets the package manifest + * + * @return the package manifest + */ + public Manifest getManifest() + { + return manifest; + } + + /** + * Remove the temporary entry files + * + * @throws IOException + */ + public void removeTemporaryEntryFiles() throws IOException + { + Set<String> tempEntries = + new HashSet<String>(Collections.unmodifiableSet(tempEntryMap.keySet())); + for (String tempEntry : tempEntries) + { + removeEntryFile(tempEntry); + } + } + + /* + * Delete a single file from the filesystem. + * + * @param fileToDelete + * A <code>File</code> object representing the file to be + * deleted. + * @throws IOException + * if any problem occurs deleting the file. + */ + private void deleteFile(File fileToDelete) throws IOException + { + if ((fileToDelete != null) && fileToDelete.exists() && fileToDelete.isFile() + && fileToDelete.canWrite()) + { + fileToDelete.delete(); + } + else + { + String errorMessage = ""; + if (fileToDelete == null) + { + errorMessage = "Null pointer for file to delete."; + } + else + { + if (!fileToDelete.exists()) + { + errorMessage = "The file " + fileToDelete.getName() + " does not exist."; + } + else + { + if (!fileToDelete.isFile()) + { + errorMessage = fileToDelete.getName() + " is not a file."; + } + else + { + if (!fileToDelete.canWrite()) + { + errorMessage = "Cannot write to " + fileToDelete.getName(); + } + } + } + + } + throw new IOException("Cannot delete file: " + errorMessage); + } + } + + /** + * Create a new Manifest with the basic values and the created by string + * @param createdBy who is creating this manifest + * @return a new Manifest with basic values and desired created by string + */ + public Manifest createManifest(String createdBy) + { + Manifest newManifest = new Manifest(); + Attributes mainAttributes = newManifest.getMainAttributes(); + mainAttributes.putValue(Attributes.Name.MANIFEST_VERSION.toString(), + CertificateManagerActivator.MANIFEST_VERSION); + mainAttributes.putValue(CertificateManagerActivator.CREATED_BY_FIELD, createdBy); + return newManifest; + } + + /** + * Execute the zipalign for a certain apk + * @param apk + */ + public static void zipAlign(File apk) + { + // String zipAlign = SdkUtils.getSdkToolsPath(); + String zipAlign = + AndroidUtils.getSDKPathByPreference() + Path.SEPARATOR + IAndroidConstants.FD_TOOLS; + try + { + File tempFile = File.createTempFile("_tozipalign", ".apk"); + FileUtil.copyFile(apk, tempFile); + + if (!zipAlign.endsWith(File.separator)) + { + zipAlign += File.separator; + } + zipAlign += Platform.getOS().equals(Platform.OS_WIN32) ? "zipalign.exe" : "zipalign"; + + String[] command = new String[] + { + zipAlign, "-f", "-v", "4", tempFile.getAbsolutePath(), apk.getAbsolutePath() + }; + StringBuilder commandLine = new StringBuilder(); + for (String commandPart : command) + { + commandLine.append(commandPart); + commandLine.append(" "); + } + + StudioLogger.info(PackageFile.class, "Zipaligning package: " + commandLine.toString()); + Runtime.getRuntime().exec(command); + } + catch (IOException e) + { + StudioLogger.error(PackageFile.class, "Error while zipaligning package", e); + } + catch (Exception e) + { + StudioLogger.error(PackageFile.class, + "Zipalign application cannot be executed - insuficient permissions", e); + } + } +} |