Java tutorial
/* * Copyright (C) 2016 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.packaging; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import com.android.SdkConstants; import com.android.annotations.NonNull; import com.android.builder.core.DefaultManifestParser; import com.android.builder.model.AaptOptions; import com.google.common.base.Charsets; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.hash.HashFunction; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; import com.google.common.io.Files; import java.io.File; import java.io.IOException; import java.util.List; /** * Utility class for packaging. */ public class PackagingUtils { /** * Set of file formats which are already compressed, or don't compress well, same as the one * used by aapt. */ public static final ImmutableSet<String> DEFAULT_DONT_COMPRESS_EXTENSIONS = ImmutableSet.of(".jpg", ".jpeg", ".png", ".gif", ".wav", ".mp2", ".mp3", ".ogg", ".aac", ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", ".rtttl", ".imy", ".xmf", ".mp4", ".m4a", ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"); /** * Checks a file to make sure it should be packaged as standard resources. * @param filePath OS-independent path of the file (including extension), relative to the * archive * @param allowClassFiles whether to allow java class files * @return true if the file should be packaged as standard java resources */ public static boolean checkFileForApkPackaging(@NonNull String filePath, boolean allowClassFiles) { String fileName = new File(filePath).getName(); // ignore hidden files and backup files return !isOfNonResourcesExtensions(Files.getFileExtension(fileName), allowClassFiles) && !filePath.equals("META-INF/MANIFEST.MF") && !isUsedForSigning(filePath) && !isMavenMetadata(filePath); } private static boolean isMavenMetadata(String filePath) { return filePath.startsWith("META-INF/maven"); } private static boolean isUsedForSigning(String filePath) { if (!"META-INF".equals(new File(filePath).getParent())) { return false; } String fileExtension = Files.getFileExtension(filePath); for (String extension : SIGNING_EXTENSIONS) { if (fileExtension.equalsIgnoreCase(extension)) { return true; } } return false; } private static boolean isOfNonResourcesExtensions(@NonNull String extension, boolean allowClassFiles) { for (String ext : NON_RESOURCES_EXTENSIONS) { if (ext.equalsIgnoreCase(extension)) { return true; } } return !allowClassFiles && SdkConstants.EXT_CLASS.equals(extension); } /** * List of file extensions that represents non resources files. */ private static final ImmutableList<String> NON_RESOURCES_EXTENSIONS = ImmutableList.<String>builder() .add("aidl") // Aidl files .add("rs") // RenderScript files .add("fs") // FilterScript files .add("rsh") // RenderScript header files .add("d") // Dependency files .add("java") // Java files .add("scala") // Scala files .add("scc") // VisualSourceSafe .add("swp") // vi swap file .build(); /** * List of file extensions that are used for jar signing. */ public static final ImmutableList<String> SIGNING_EXTENSIONS = ImmutableList.of("SF", "RSA", "DSA", "EC"); /** * Computes an "application hash", a reasonably unique identifier for an app. * <p> * This is currently used by Instant Run to prevent apps on a device from guessing * the authentication token associated with an instant run developed app on the same * device. * <p> * This method creates the "secret", the field in the AppInfo class which is used as a simple * authentication token between the IDE and the app server. * <p> * This is not a cryptographically strong unique id; we could attempt to make a truly random * number here, but we'd need to store that id somewhere such that subsequent builds * will use the same secret, to avoid having the IDE and the app getting out of sync, * and there isn't really a natural place for us to store the key to make it survive across * a clean build. (One possibility is putting it into local.properties). * <p> * However, we have much simpler needs: we just need a key that can't be guessed from * a hostile app on the developer's device, and it only has a few guesses (after providing * the wrong secret to the server a few times, the server will shut down.) We can't * rely on the package name alone, since the port number is known, and the package name is * discoverable by the hostile app (by querying the contents of /data/data/*). Therefore * we also include facts that the hostile app can't know, such as as the path on the * developer's machine to the app project and the name of the developer's machine, etc. * The goal is for this secret to be reasonably stable (e.g. the same from build to build) * yet not something an app could guess if it only has a couple of tries. */ public static long computeApplicationHash(@NonNull File projectDir) { HashFunction hashFunction = Hashing.md5(); Hasher hasher = hashFunction.newHasher(); try { projectDir = projectDir.getCanonicalFile(); } catch (IOException ignore) { // If this throws an exception the assignment won't // be done and we'll stick with the regular project dir } String path = projectDir.getPath(); hasher.putBytes(path.getBytes(Charsets.UTF_8)); String user = System.getProperty("user.name"); if (user != null) { hasher.putBytes(user.getBytes(Charsets.UTF_8)); } return hasher.hash().asLong(); } @NonNull public static Predicate<String> getDefaultNoCompressPredicate() { return getNoCompressPredicateForExtensions(DEFAULT_DONT_COMPRESS_EXTENSIONS); } @NonNull public static Predicate<String> getNoCompressPredicate(@NonNull AaptOptions aaptOptions, @NonNull File manifest) { checkNotNull(aaptOptions); checkState(manifest.exists()); NativeLibrariesPackagingMode packagingMode = getNativeLibrariesLibrariesPackagingMode(manifest); return getNoCompressPredicateForExtensions(getAllNoCompressExtensions(aaptOptions, packagingMode)); } @NonNull public static NativeLibrariesPackagingMode getNativeLibrariesLibrariesPackagingMode(@NonNull File manifest) { checkState(manifest.exists()); DefaultManifestParser parser = new DefaultManifestParser(manifest); Boolean extractNativeLibs = parser.getExtractNativeLibs(); // The default is "true", so we only package *.so files differently if the user explicitly // set this to "false". if (Boolean.FALSE.equals(extractNativeLibs)) { return NativeLibrariesPackagingMode.UNCOMPRESSED_AND_ALIGNED; } else { return NativeLibrariesPackagingMode.COMPRESSED; } } @NonNull private static Predicate<String> getNoCompressPredicateForExtensions( @NonNull Iterable<String> noCompressExtensions) { return name -> { for (String extension : noCompressExtensions) { //noinspection StringToUpperCaseOrToLowerCaseWithoutLocale - extensions are ASCII if (name.toLowerCase().endsWith(extension)) { return true; } } return false; }; } @NonNull private static List<String> getAllNoCompressExtensions(@NonNull AaptOptions aaptOptions, @NonNull NativeLibrariesPackagingMode nativeLibrariesPackagingMode) { List<String> result = Lists.newArrayList(DEFAULT_DONT_COMPRESS_EXTENSIONS); if (nativeLibrariesPackagingMode == NativeLibrariesPackagingMode.UNCOMPRESSED_AND_ALIGNED) { result.add(SdkConstants.DOT_NATIVE_LIBS); } if (aaptOptions.getNoCompress() != null) { result.addAll(aaptOptions.getNoCompress()); } return result; } }