upload android base code part1

This commit is contained in:
August 2018-08-08 15:50:00 +08:00
parent e02f198e2d
commit 0a1de6c4b3
48159 changed files with 9071466 additions and 0 deletions

View file

@ -0,0 +1,19 @@
# Copyright (C) 2014 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.
LOCAL_PATH:= $(call my-dir)
# Don't include in unbundled build.
ifeq ($(TARGET_BUILD_APPS),)
include $(call all-makefiles-under,$(LOCAL_PATH))
endif

View file

@ -0,0 +1,28 @@
=========================================================================
== NOTICE file corresponding to the section 4 d of ==
== the Apache License, Version 2.0, ==
== in this case for the Android-specific code. ==
=========================================================================
Android Code
Copyright 2005-2014 The Android Open Source Project
This product includes software developed as part of
The Android Open Source Project (http://source.android.com).
=========================================================================
== NOTICE file corresponding to the section 4 d of ==
== the Apache License, Version 2.0, ==
== in this case for the Apache Harmony distribution. ==
=========================================================================
Apache Harmony
Copyright 2006 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).
Portions of Harmony were originally developed by
Intel Corporation and are licensed to the Apache Software
Foundation under the "Software Grant and Corporate Contribution
License Agreement", informally known as the "Intel Harmony CLA".

View file

@ -0,0 +1,4 @@
aqj@google.com
benoitlamarche@google.com
jplesot@google.com
yroussel@google.com

View file

@ -0,0 +1,6 @@
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
[Builtin Hooks]
commit_msg_changeid_field = true
commit_msg_test_field = true

View file

@ -0,0 +1,99 @@
/*
* Copyright (C) 2017 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.
*/
import org.gradle.internal.os.OperatingSystem
buildscript {
repositories {
maven { url '../../prebuilts/gradle-plugin' }
maven { url '../../prebuilts/tools/common/m2/repository' }
maven { url '../../prebuilts/tools/common/m2/internal' }
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
}
}
apply from: 'version.gradle'
final String platform = OperatingSystem.current().isMacOsX() ? 'darwin' : 'linux'
System.setProperty('android.dir', "${rootDir}/../../")
final String fullSdkPath = "${rootDir}/../../prebuilts/fullsdk-${platform}"
if (file(fullSdkPath).exists()) {
gradle.ext.currentSdk = 26
ext.buildToolsVersion = '26.0.0'
project.ext.androidJar = files("${fullSdkPath}/platforms/android-${gradle.currentSdk}/android.jar")
System.setProperty('android.home', "${rootDir}/../../prebuilts/fullsdk-${platform}")
File props = file("local.properties")
props.write "sdk.dir=${fullSdkPath}"
} else {
gradle.ext.currentSdk = 'current'
ext.buildToolsVersion = '26.0.0'
project.ext.androidJar = files("${project.rootDir}/../../prebuilts/sdk/current/android.jar")
File props = file("local.properties")
props.write "android.dir=../../"
}
/*
* With the build server you are given two env variables.
* The OUT_DIR is a temporary directory you can use to put things during the build.
* The DIST_DIR is where you want to save things from the build.
*
* The build server will copy the contents of DIST_DIR to somewhere and make it available.
*/
if (System.env.DIST_DIR != null && System.env.OUT_DIR != null) {
buildDir = new File(System.env.OUT_DIR + '/gradle/frameworks/support/build').getCanonicalFile()
project.ext.distDir = new File(System.env.DIST_DIR).getCanonicalFile()
} else {
buildDir = file('../../out/host/gradle/frameworks/support/build')
project.ext.distDir = file('../../out/dist')
}
ext.supportRepoOut = new File(buildDir, 'support_repo')
// upload anchor for subprojects to upload their artifacts
// to the local repo.
task dist(type: Zip) {
group = BasePlugin.BUILD_GROUP
description 'Builds distribution artifacts.'
from project.ext.supportRepoOut
into 'm2repository'
destinationDir project.ext.distDir
baseName = String.format("top-of-tree-m2repository-%s", project.multidexVersion)
doLast {
logger.warn "Compressed maven artifacts to ${archivePath}"
}
}
subprojects {
// Change buildDir first so that all plugins pick up the new value.
project.buildDir = project.file("${project.parent.buildDir}/../${project.name}/build")
apply plugin: 'maven'
version = rootProject.multidexVersion
group = 'com.android.support'
dist.dependsOn project.tasks.uploadArchives
project.plugins.whenPluginAdded { plugin ->
if ("com.android.build.gradle.LibraryPlugin".equals(plugin.class.name)) {
project.android.buildToolsVersion = rootProject.buildToolsVersion
}
}
}

Binary file not shown.

View file

@ -0,0 +1,6 @@
#Tue Aug 16 10:43:36 PDT 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=../../../../tools/external/gradle/gradle-3.3-bin.zip

174
android/frameworks/multidex/gradlew vendored Executable file
View file

@ -0,0 +1,174 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
# Change the project's .gradle to the android out dir.
ANDROID_GRADLE_ROOT="$APP_HOME/../../out/host/gradle/frameworks/support"
if [[ -z "$ANDROID_CACHE_DIR" ]]; then
ANDROID_CACHE_DIR="$ANDROID_GRADLE_ROOT/.gradle"
fi
# Change the local user directories to be under the android out dir
export GRADLE_USER_HOME="$ANDROID_GRADLE_ROOT/.gradle"
export M2_HOME="$ANDROID_GRADLE_ROOT/.m2"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain --project-cache-dir=$ANDROID_CACHE_DIR "$@"

View file

@ -0,0 +1,34 @@
# Copyright (C) 2013 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.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := android-support-multidex-instrumentation
LOCAL_JAVA_LIBRARIES := android-support-multidex
LOCAL_SDK_VERSION := 4
LOCAL_SRC_FILES := $(call all-java-files-under, src)
ASMDI_GIT_VERSION_TAG := `cd $(LOCAL_PATH); git log --format="%H" -n 1 || (echo git hash not available; exit 0)`
ASMDI_VERSION_INTERMEDIATE = $(call intermediates-dir-for,JAVA_LIBRARIES,$(LOCAL_MODULE),,COMMON)/$(LOCAL_MODULE).version.txt
$(ASMDI_VERSION_INTERMEDIATE):
$(hide) mkdir -p $(dir $@)
$(hide) echo "git.version=$(ASMDI_GIT_VERSION_TAG)" > $@
LOCAL_JAVA_RESOURCE_FILES := $(ASMDI_VERSION_INTERMEDIATE)
include $(BUILD_STATIC_JAVA_LIBRARY)

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2013 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.support.multidex.instrumentation">
</manifest>

View file

@ -0,0 +1,10 @@
Library Project including compatibility IntrumentationTestRunner
for multiple dex applications.
This can be used by an Android test project to set up the classloader
of applications with multiple dexes.
There is technically no source, but the src folder is necessary
to ensure that the build system works. The content is actually
located in libs/android-support-multidex-instrumentation.jar.

View file

@ -0,0 +1,77 @@
/*
* Copyright (C) 2017 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.
*/
apply plugin: 'com.android.library'
dependencies {
compile project(':multidex')
}
android {
compileSdkVersion 4
defaultConfig {
minSdkVersion 4
}
sourceSets {
main {
java.srcDirs = ['src']
resources.srcDirs = ['res']
res.srcDirs = ['src']
manifest.srcFile 'AndroidManifest.xml'
}
}
lintOptions {
// TODO: fix errors and reenable.
abortOnError false
}
}
uploadArchives {
repositories {
mavenDeployer {
repository(url: uri(rootProject.ext.supportRepoOut)) {
}
pom.project {
name 'Android Multi-Dex Instrumentation Library'
description "Library for legacy multi-dex support"
url ''
inceptionYear '2013'
licenses {
license {
name 'The Apache Software License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
distribution 'repo'
}
}
scm {
url "http://source.android.com"
connection "scm:git:https://android.googlesource.com/platform/frameworks/multidex"
}
developers {
developer {
name 'The Android Open Source Project'
}
}
}
}
}
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (C) 2013 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.test.runner;
import android.os.Bundle;
import android.support.multidex.MultiDex;
import android.test.InstrumentationTestRunner;
/**
* {@link android.test.InstrumentationTestRunner} for testing application needing multidex support.
*
* @deprecated Use
* <a href="{@docRoot}reference/android/support/test/runner/AndroidJUnitRunner.html">
* AndroidJUnitRunner</a> instead. New tests should be written using the
* <a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a>.
*/
public class MultiDexTestRunner extends InstrumentationTestRunner {
@Override
public void onCreate(Bundle arguments) {
MultiDex.installInstrumentation(getContext(), getTargetContext());
super.onCreate(arguments);
}
}

View file

@ -0,0 +1,35 @@
# Copyright (C) 2013 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.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := android-support-multidex
LOCAL_SDK_VERSION := 4
LOCAL_SRC_FILES := $(call all-java-files-under, src)
ASMD_GIT_VERSION_TAG := `cd $(LOCAL_PATH); git log --format="%H" -n 1 || (echo git hash not available; exit 0)`
ASMD_VERSION_INTERMEDIATE = $(call intermediates-dir-for,JAVA_LIBRARIES,$(LOCAL_MODULE),,COMMON)/$(LOCAL_MODULE).version.txt
$(ASMD_VERSION_INTERMEDIATE):
$(hide) mkdir -p $(dir $@)
$(hide) echo "git.version=$(ASMD_GIT_VERSION_TAG)" > $@
LOCAL_JAVA_RESOURCE_FILES := $(ASMD_VERSION_INTERMEDIATE)
LOCAL_JACK_FLAGS:=--import-meta $(LOCAL_PATH)/jack-meta
LOCAL_ADDITIONAL_DEPENDENCIES := $(wildcard $(LOCAL_PATH)/jack-meta/*)
include $(BUILD_STATIC_JAVA_LIBRARY)

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2013 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.support.multidex">
</manifest>

View file

@ -0,0 +1,15 @@
Library Project including a multidex loader.
This can be used by an Android project to install multiple dexes
in the classloader of an application running on API 4+.
Note that multidexing will allow to go over the dex index limit.
It can also help with the linearalloc limit during installation but it
won't help with linearalloc at execution time. This means that
most applications requiring multidexing because of the dex index
limit won't execute on API below 14 because of linearalloc limit.
There is technically no source, but the src folder is necessary
to ensure that the build system works. The content is actually
located in libs/android-support-multidex.jar.

View file

@ -0,0 +1,73 @@
/*
* Copyright (C) 2017 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.
*/
apply plugin: 'com.android.library'
android {
compileSdkVersion 4
defaultConfig {
minSdkVersion 4
}
sourceSets {
main {
java.srcDirs = ['src']
resources.srcDirs = ['res']
res.srcDirs = ['src']
manifest.srcFile 'AndroidManifest.xml'
}
}
lintOptions {
// TODO: fix errors and reenable.
abortOnError false
}
}
uploadArchives {
repositories {
mavenDeployer {
repository(url: uri(rootProject.ext.supportRepoOut)) {
}
pom.project {
name 'Android Multi-Dex Library'
description "Library for legacy multi-dex support"
url ''
inceptionYear '2013'
licenses {
license {
name 'The Apache Software License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
distribution 'repo'
}
}
scm {
url "http://source.android.com"
connection "scm:git:https://android.googlesource.com/platform/frameworks/multidex"
}
developers {
developer {
name 'The Android Open Source Project'
}
}
}
}
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (C) 2014 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.
*/
multidexInstanciable:
@@com.android.jack.annotations.MultiDexInstaller
class *
is {
public
}
extends {
class android.app.Instrumentation
| class android.app.Activity
| class android.app.Service
| class android.content.ContentProvider
| class android.content.BroadcastReceiver
| class android.app.backup.BackupAgent
| class android.app.Application
}
do {
@@com.android.jack.annotations.MultiDexInstaller
method void <init>();
}
multidexInstaller:
class *
is {
public
}
extends {
class android.app.Application
}
do {
@@com.android.jack.annotations.MultiDexInstaller
method void attachBaseContext(class android.content.Context)
;
}
instrumentationTestCase:
@@com.android.jack.annotations.ForceInMainDex
class *
extends {
class android.test.InstrumentationTestCase
}

View file

@ -0,0 +1,602 @@
/*
* Copyright (C) 2013 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 android.support.multidex;
import android.app.Application;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.util.Log;
import dalvik.system.DexFile;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipFile;
/**
* MultiDex patches {@link Context#getClassLoader() the application context class
* loader} in order to load classes from more than one dex file. The primary
* {@code classes.dex} must contain the classes necessary for calling this
* class methods. Secondary dex files named classes2.dex, classes3.dex... found
* in the application apk will be added to the classloader after first call to
* {@link #install(Context)}.
*
* <p/>
* This library provides compatibility for platforms with API level 4 through 20. This library does
* nothing on newer versions of the platform which provide built-in support for secondary dex files.
*/
public final class MultiDex {
static final String TAG = "MultiDex";
private static final String OLD_SECONDARY_FOLDER_NAME = "secondary-dexes";
private static final String CODE_CACHE_NAME = "code_cache";
private static final String CODE_CACHE_SECONDARY_FOLDER_NAME = "secondary-dexes";
private static final int MAX_SUPPORTED_SDK_VERSION = 20;
private static final int MIN_SDK_VERSION = 4;
private static final int VM_WITH_MULTIDEX_VERSION_MAJOR = 2;
private static final int VM_WITH_MULTIDEX_VERSION_MINOR = 1;
private static final String NO_KEY_PREFIX = "";
private static final Set<File> installedApk = new HashSet<File>();
private static final boolean IS_VM_MULTIDEX_CAPABLE =
isVMMultidexCapable(System.getProperty("java.vm.version"));
private MultiDex() {}
/**
* Patches the application context class loader by appending extra dex files
* loaded from the application apk. This method should be called in the
* attachBaseContext of your {@link Application}, see
* {@link MultiDexApplication} for more explanation and an example.
*
* @param context application context.
* @throws RuntimeException if an error occurred preventing the classloader
* extension.
*/
public static void install(Context context) {
Log.i(TAG, "Installing application");
if (IS_VM_MULTIDEX_CAPABLE) {
Log.i(TAG, "VM has multidex support, MultiDex support library is disabled.");
return;
}
if (Build.VERSION.SDK_INT < MIN_SDK_VERSION) {
throw new RuntimeException("MultiDex installation failed. SDK " + Build.VERSION.SDK_INT
+ " is unsupported. Min SDK version is " + MIN_SDK_VERSION + ".");
}
try {
ApplicationInfo applicationInfo = getApplicationInfo(context);
if (applicationInfo == null) {
Log.i(TAG, "No ApplicationInfo available, i.e. running on a test Context:"
+ " MultiDex support library is disabled.");
return;
}
doInstallation(context,
new File(applicationInfo.sourceDir),
new File(applicationInfo.dataDir),
CODE_CACHE_SECONDARY_FOLDER_NAME,
NO_KEY_PREFIX);
} catch (Exception e) {
Log.e(TAG, "MultiDex installation failure", e);
throw new RuntimeException("MultiDex installation failed (" + e.getMessage() + ").");
}
Log.i(TAG, "install done");
}
/**
* Patches the instrumentation context class loader by appending extra dex files
* loaded from the instrumentation apk and the application apk. This method should be called in
* the onCreate of your {@link Instrumentation}, see
* {@link com.android.test.runner.MultiDexTestRunner} for an example.
*
* @param instrumentationContext instrumentation context.
* @param targetContext target application context.
* @throws RuntimeException if an error occurred preventing the classloader
* extension.
*/
public static void installInstrumentation(Context instrumentationContext,
Context targetContext) {
Log.i(TAG, "Installing instrumentation");
if (IS_VM_MULTIDEX_CAPABLE) {
Log.i(TAG, "VM has multidex support, MultiDex support library is disabled.");
return;
}
if (Build.VERSION.SDK_INT < MIN_SDK_VERSION) {
throw new RuntimeException("MultiDex installation failed. SDK " + Build.VERSION.SDK_INT
+ " is unsupported. Min SDK version is " + MIN_SDK_VERSION + ".");
}
try {
ApplicationInfo instrumentationInfo = getApplicationInfo(instrumentationContext);
if (instrumentationInfo == null) {
Log.i(TAG, "No ApplicationInfo available for instrumentation, i.e. running on a"
+ " test Context: MultiDex support library is disabled.");
return;
}
ApplicationInfo applicationInfo = getApplicationInfo(targetContext);
if (applicationInfo == null) {
Log.i(TAG, "No ApplicationInfo available, i.e. running on a test Context:"
+ " MultiDex support library is disabled.");
return;
}
String instrumentationPrefix = instrumentationContext.getPackageName() + ".";
File dataDir = new File(applicationInfo.dataDir);
doInstallation(targetContext,
new File(instrumentationInfo.sourceDir),
dataDir,
instrumentationPrefix + CODE_CACHE_SECONDARY_FOLDER_NAME,
instrumentationPrefix);
doInstallation(targetContext,
new File(applicationInfo.sourceDir),
dataDir,
CODE_CACHE_SECONDARY_FOLDER_NAME,
NO_KEY_PREFIX);
} catch (Exception e) {
Log.e(TAG, "MultiDex installation failure", e);
throw new RuntimeException("MultiDex installation failed (" + e.getMessage() + ").");
}
Log.i(TAG, "Installation done");
}
/**
* @param mainContext context used to get filesDir, to save preference and to get the
* classloader to patch.
* @param sourceApk Apk file.
* @param dataDir data directory to use for code cache simulation.
* @param secondaryFolderName name of the folder for storing extractions.
* @param prefsKeyPrefix prefix of all stored preference keys.
*/
private static void doInstallation(Context mainContext, File sourceApk, File dataDir,
String secondaryFolderName, String prefsKeyPrefix) throws IOException,
IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
InvocationTargetException, NoSuchMethodException {
synchronized (installedApk) {
if (installedApk.contains(sourceApk)) {
return;
}
installedApk.add(sourceApk);
if (Build.VERSION.SDK_INT > MAX_SUPPORTED_SDK_VERSION) {
Log.w(TAG, "MultiDex is not guaranteed to work in SDK version "
+ Build.VERSION.SDK_INT + ": SDK version higher than "
+ MAX_SUPPORTED_SDK_VERSION + " should be backed by "
+ "runtime with built-in multidex capabilty but it's not the "
+ "case here: java.vm.version=\""
+ System.getProperty("java.vm.version") + "\"");
}
/* The patched class loader is expected to be a descendant of
* dalvik.system.BaseDexClassLoader. We modify its
* dalvik.system.DexPathList pathList field to append additional DEX
* file entries.
*/
ClassLoader loader;
try {
loader = mainContext.getClassLoader();
} catch (RuntimeException e) {
/* Ignore those exceptions so that we don't break tests relying on Context like
* a android.test.mock.MockContext or a android.content.ContextWrapper with a
* null base Context.
*/
Log.w(TAG, "Failure while trying to obtain Context class loader. " +
"Must be running in test mode. Skip patching.", e);
return;
}
if (loader == null) {
// Note, the context class loader is null when running Robolectric tests.
Log.e(TAG,
"Context class loader is null. Must be running in test mode. "
+ "Skip patching.");
return;
}
try {
clearOldDexDir(mainContext);
} catch (Throwable t) {
Log.w(TAG, "Something went wrong when trying to clear old MultiDex extraction, "
+ "continuing without cleaning.", t);
}
File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName);
List<? extends File> files =
MultiDexExtractor.load(mainContext, sourceApk, dexDir, prefsKeyPrefix, false);
installSecondaryDexes(loader, dexDir, files);
}
}
private static ApplicationInfo getApplicationInfo(Context context) {
try {
/* Due to package install races it is possible for a process to be started from an old
* apk even though that apk has been replaced. Querying for ApplicationInfo by package
* name may return information for the new apk, leading to a runtime with the old main
* dex file and new secondary dex files. This leads to various problems like
* ClassNotFoundExceptions. Using context.getApplicationInfo() should result in the
* process having a consistent view of the world (even if it is of the old world). The
* package install races are eventually resolved and old processes are killed.
*/
return context.getApplicationInfo();
} catch (RuntimeException e) {
/* Ignore those exceptions so that we don't break tests relying on Context like
* a android.test.mock.MockContext or a android.content.ContextWrapper with a null
* base Context.
*/
Log.w(TAG, "Failure while trying to obtain ApplicationInfo from Context. " +
"Must be running in test mode. Skip patching.", e);
return null;
}
}
/**
* Identifies if the current VM has a native support for multidex, meaning there is no need for
* additional installation by this library.
* @return true if the VM handles multidex
*/
/* package visible for test */
static boolean isVMMultidexCapable(String versionString) {
boolean isMultidexCapable = false;
if (versionString != null) {
Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString);
if (matcher.matches()) {
try {
int major = Integer.parseInt(matcher.group(1));
int minor = Integer.parseInt(matcher.group(2));
isMultidexCapable = (major > VM_WITH_MULTIDEX_VERSION_MAJOR)
|| ((major == VM_WITH_MULTIDEX_VERSION_MAJOR)
&& (minor >= VM_WITH_MULTIDEX_VERSION_MINOR));
} catch (NumberFormatException e) {
// let isMultidexCapable be false
}
}
}
Log.i(TAG, "VM with version " + versionString +
(isMultidexCapable ?
" has multidex support" :
" does not have multidex support"));
return isMultidexCapable;
}
private static void installSecondaryDexes(ClassLoader loader, File dexDir,
List<? extends File> files)
throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
InvocationTargetException, NoSuchMethodException, IOException {
if (!files.isEmpty()) {
if (Build.VERSION.SDK_INT >= 19) {
V19.install(loader, files, dexDir);
} else if (Build.VERSION.SDK_INT >= 14) {
V14.install(loader, files, dexDir);
} else {
V4.install(loader, files);
}
}
}
/**
* Locates a given field anywhere in the class inheritance hierarchy.
*
* @param instance an object to search the field into.
* @param name field name
* @return a field object
* @throws NoSuchFieldException if the field cannot be located
*/
private static Field findField(Object instance, String name) throws NoSuchFieldException {
for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
try {
Field field = clazz.getDeclaredField(name);
if (!field.isAccessible()) {
field.setAccessible(true);
}
return field;
} catch (NoSuchFieldException e) {
// ignore and search next
}
}
throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
}
/**
* Locates a given method anywhere in the class inheritance hierarchy.
*
* @param instance an object to search the method into.
* @param name method name
* @param parameterTypes method parameter types
* @return a method object
* @throws NoSuchMethodException if the method cannot be located
*/
private static Method findMethod(Object instance, String name, Class<?>... parameterTypes)
throws NoSuchMethodException {
for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
try {
Method method = clazz.getDeclaredMethod(name, parameterTypes);
if (!method.isAccessible()) {
method.setAccessible(true);
}
return method;
} catch (NoSuchMethodException e) {
// ignore and search next
}
}
throw new NoSuchMethodException("Method " + name + " with parameters " +
Arrays.asList(parameterTypes) + " not found in " + instance.getClass());
}
/**
* Replace the value of a field containing a non null array, by a new array containing the
* elements of the original array plus the elements of extraElements.
* @param instance the instance whose field is to be modified.
* @param fieldName the field to modify.
* @param extraElements elements to append at the end of the array.
*/
private static void expandFieldArray(Object instance, String fieldName,
Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
Field jlrField = findField(instance, fieldName);
Object[] original = (Object[]) jlrField.get(instance);
Object[] combined = (Object[]) Array.newInstance(
original.getClass().getComponentType(), original.length + extraElements.length);
System.arraycopy(original, 0, combined, 0, original.length);
System.arraycopy(extraElements, 0, combined, original.length, extraElements.length);
jlrField.set(instance, combined);
}
private static void clearOldDexDir(Context context) throws Exception {
File dexDir = new File(context.getFilesDir(), OLD_SECONDARY_FOLDER_NAME);
if (dexDir.isDirectory()) {
Log.i(TAG, "Clearing old secondary dex dir (" + dexDir.getPath() + ").");
File[] files = dexDir.listFiles();
if (files == null) {
Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ").");
return;
}
for (File oldFile : files) {
Log.i(TAG, "Trying to delete old file " + oldFile.getPath() + " of size "
+ oldFile.length());
if (!oldFile.delete()) {
Log.w(TAG, "Failed to delete old file " + oldFile.getPath());
} else {
Log.i(TAG, "Deleted old file " + oldFile.getPath());
}
}
if (!dexDir.delete()) {
Log.w(TAG, "Failed to delete secondary dex dir " + dexDir.getPath());
} else {
Log.i(TAG, "Deleted old secondary dex dir " + dexDir.getPath());
}
}
}
private static File getDexDir(Context context, File dataDir, String secondaryFolderName)
throws IOException {
File cache = new File(dataDir, CODE_CACHE_NAME);
try {
mkdirChecked(cache);
} catch (IOException e) {
/* If we can't emulate code_cache, then store to filesDir. This means abandoning useless
* files on disk if the device ever updates to android 5+. But since this seems to
* happen only on some devices running android 2, this should cause no pollution.
*/
cache = new File(context.getFilesDir(), CODE_CACHE_NAME);
mkdirChecked(cache);
}
File dexDir = new File(cache, secondaryFolderName);
mkdirChecked(dexDir);
return dexDir;
}
private static void mkdirChecked(File dir) throws IOException {
dir.mkdir();
if (!dir.isDirectory()) {
File parent = dir.getParentFile();
if (parent == null) {
Log.e(TAG, "Failed to create dir " + dir.getPath() + ". Parent file is null.");
} else {
Log.e(TAG, "Failed to create dir " + dir.getPath() +
". parent file is a dir " + parent.isDirectory() +
", a file " + parent.isFile() +
", exists " + parent.exists() +
", readable " + parent.canRead() +
", writable " + parent.canWrite());
}
throw new IOException("Failed to create directory " + dir.getPath());
}
}
/**
* Installer for platform versions 19.
*/
private static final class V19 {
private static void install(ClassLoader loader,
List<? extends File> additionalClassPathEntries,
File optimizedDirectory)
throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
/* The patched class loader is expected to be a descendant of
* dalvik.system.BaseDexClassLoader. We modify its
* dalvik.system.DexPathList pathList field to append additional DEX
* file entries.
*/
Field pathListField = findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
suppressedExceptions));
if (suppressedExceptions.size() > 0) {
for (IOException e : suppressedExceptions) {
Log.w(TAG, "Exception in makeDexElement", e);
}
Field suppressedExceptionsField =
findField(dexPathList, "dexElementsSuppressedExceptions");
IOException[] dexElementsSuppressedExceptions =
(IOException[]) suppressedExceptionsField.get(dexPathList);
if (dexElementsSuppressedExceptions == null) {
dexElementsSuppressedExceptions =
suppressedExceptions.toArray(
new IOException[suppressedExceptions.size()]);
} else {
IOException[] combined =
new IOException[suppressedExceptions.size() +
dexElementsSuppressedExceptions.length];
suppressedExceptions.toArray(combined);
System.arraycopy(dexElementsSuppressedExceptions, 0, combined,
suppressedExceptions.size(), dexElementsSuppressedExceptions.length);
dexElementsSuppressedExceptions = combined;
}
suppressedExceptionsField.set(dexPathList, dexElementsSuppressedExceptions);
}
}
/**
* A wrapper around
* {@code private static final dalvik.system.DexPathList#makeDexElements}.
*/
private static Object[] makeDexElements(
Object dexPathList, ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
Method makeDexElements =
findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class,
ArrayList.class);
return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory,
suppressedExceptions);
}
}
/**
* Installer for platform versions 14, 15, 16, 17 and 18.
*/
private static final class V14 {
private static void install(ClassLoader loader,
List<? extends File> additionalClassPathEntries,
File optimizedDirectory)
throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
/* The patched class loader is expected to be a descendant of
* dalvik.system.BaseDexClassLoader. We modify its
* dalvik.system.DexPathList pathList field to append additional DEX
* file entries.
*/
Field pathListField = findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
new ArrayList<File>(additionalClassPathEntries), optimizedDirectory));
}
/**
* A wrapper around
* {@code private static final dalvik.system.DexPathList#makeDexElements}.
*/
private static Object[] makeDexElements(
Object dexPathList, ArrayList<File> files, File optimizedDirectory)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
Method makeDexElements =
findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class);
return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory);
}
}
/**
* Installer for platform versions 4 to 13.
*/
private static final class V4 {
private static void install(ClassLoader loader,
List<? extends File> additionalClassPathEntries)
throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, IOException {
/* The patched class loader is expected to be a descendant of
* dalvik.system.DexClassLoader. We modify its
* fields mPaths, mFiles, mZips and mDexs to append additional DEX
* file entries.
*/
int extraSize = additionalClassPathEntries.size();
Field pathField = findField(loader, "path");
StringBuilder path = new StringBuilder((String) pathField.get(loader));
String[] extraPaths = new String[extraSize];
File[] extraFiles = new File[extraSize];
ZipFile[] extraZips = new ZipFile[extraSize];
DexFile[] extraDexs = new DexFile[extraSize];
for (ListIterator<? extends File> iterator = additionalClassPathEntries.listIterator();
iterator.hasNext();) {
File additionalEntry = iterator.next();
String entryPath = additionalEntry.getAbsolutePath();
path.append(':').append(entryPath);
int index = iterator.previousIndex();
extraPaths[index] = entryPath;
extraFiles[index] = additionalEntry;
extraZips[index] = new ZipFile(additionalEntry);
extraDexs[index] = DexFile.loadDex(entryPath, entryPath + ".dex", 0);
}
pathField.set(loader, path.toString());
expandFieldArray(loader, "mPaths", extraPaths);
expandFieldArray(loader, "mFiles", extraFiles);
expandFieldArray(loader, "mZips", extraZips);
expandFieldArray(loader, "mDexs", extraDexs);
}
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (C) 2014 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 android.support.multidex;
import android.app.Application;
import android.content.Context;
/**
* Minimal MultiDex capable application. To use the legacy multidex library there is 3 possibility:
* <ul>
* <li>Declare this class as the application in your AndroidManifest.xml.</li>
* <li>Have your {@link Application} extends this class.</li>
* <li>Have your {@link Application} override attachBaseContext starting with<br>
* <code>
protected void attachBaseContext(Context base) {<br>
super.attachBaseContext(base);<br>
MultiDex.install(this);
</code></li>
* <ul>
*/
public class MultiDexApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}

View file

@ -0,0 +1,422 @@
/*
* Copyright (C) 2013 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 android.support.multidex;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.util.Log;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
/**
* Exposes application secondary dex files as files in the application data
* directory.
*/
final class MultiDexExtractor {
/**
* Zip file containing one secondary dex file.
*/
private static class ExtractedDex extends File {
public long crc = NO_VALUE;
public ExtractedDex(File dexDir, String fileName) {
super(dexDir, fileName);
}
}
private static final String TAG = MultiDex.TAG;
/**
* We look for additional dex files named {@code classes2.dex},
* {@code classes3.dex}, etc.
*/
private static final String DEX_PREFIX = "classes";
private static final String DEX_SUFFIX = ".dex";
private static final String EXTRACTED_NAME_EXT = ".classes";
private static final String EXTRACTED_SUFFIX = ".zip";
private static final int MAX_EXTRACT_ATTEMPTS = 3;
private static final String PREFS_FILE = "multidex.version";
private static final String KEY_TIME_STAMP = "timestamp";
private static final String KEY_CRC = "crc";
private static final String KEY_DEX_NUMBER = "dex.number";
private static final String KEY_DEX_CRC = "dex.crc.";
private static final String KEY_DEX_TIME = "dex.time.";
/**
* Size of reading buffers.
*/
private static final int BUFFER_SIZE = 0x4000;
/* Keep value away from 0 because it is a too probable time stamp value */
private static final long NO_VALUE = -1L;
private static final String LOCK_FILENAME = "MultiDex.lock";
/**
* Extracts application secondary dexes into files in the application data
* directory.
*
* @return a list of files that were created. The list may be empty if there
* are no secondary dex files. Never return null.
* @throws IOException if encounters a problem while reading or writing
* secondary dex files
*/
static List<? extends File> load(Context context, File sourceApk, File dexDir,
String prefsKeyPrefix,
boolean forceReload) throws IOException {
Log.i(TAG, "MultiDexExtractor.load(" + sourceApk.getPath() + ", " + forceReload + ", " +
prefsKeyPrefix + ")");
long currentCrc = getZipCrc(sourceApk);
// Validity check and extraction must be done only while the lock file has been taken.
File lockFile = new File(dexDir, LOCK_FILENAME);
RandomAccessFile lockRaf = new RandomAccessFile(lockFile, "rw");
FileChannel lockChannel = null;
FileLock cacheLock = null;
List<ExtractedDex> files;
IOException releaseLockException = null;
try {
lockChannel = lockRaf.getChannel();
Log.i(TAG, "Blocking on lock " + lockFile.getPath());
cacheLock = lockChannel.lock();
Log.i(TAG, lockFile.getPath() + " locked");
if (!forceReload && !isModified(context, sourceApk, currentCrc, prefsKeyPrefix)) {
try {
files = loadExistingExtractions(context, sourceApk, dexDir, prefsKeyPrefix);
} catch (IOException ioe) {
Log.w(TAG, "Failed to reload existing extracted secondary dex files,"
+ " falling back to fresh extraction", ioe);
files = performExtractions(sourceApk, dexDir);
putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), currentCrc,
files);
}
} else {
Log.i(TAG, "Detected that extraction must be performed.");
files = performExtractions(sourceApk, dexDir);
putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), currentCrc,
files);
}
} finally {
if (cacheLock != null) {
try {
cacheLock.release();
} catch (IOException e) {
Log.e(TAG, "Failed to release lock on " + lockFile.getPath());
// Exception while releasing the lock is bad, we want to report it, but not at
// the price of overriding any already pending exception.
releaseLockException = e;
}
}
if (lockChannel != null) {
closeQuietly(lockChannel);
}
closeQuietly(lockRaf);
}
if (releaseLockException != null) {
throw releaseLockException;
}
Log.i(TAG, "load found " + files.size() + " secondary dex files");
return files;
}
/**
* Load previously extracted secondary dex files. Should be called only while owning the lock on
* {@link #LOCK_FILENAME}.
*/
private static List<ExtractedDex> loadExistingExtractions(
Context context, File sourceApk, File dexDir,
String prefsKeyPrefix)
throws IOException {
Log.i(TAG, "loading existing secondary dex files");
final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
SharedPreferences multiDexPreferences = getMultiDexPreferences(context);
int totalDexNumber = multiDexPreferences.getInt(prefsKeyPrefix + KEY_DEX_NUMBER, 1);
final List<ExtractedDex> files = new ArrayList<ExtractedDex>(totalDexNumber - 1);
for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
ExtractedDex extractedFile = new ExtractedDex(dexDir, fileName);
if (extractedFile.isFile()) {
extractedFile.crc = getZipCrc(extractedFile);
long expectedCrc = multiDexPreferences.getLong(
prefsKeyPrefix + KEY_DEX_CRC + secondaryNumber, NO_VALUE);
long expectedModTime = multiDexPreferences.getLong(
prefsKeyPrefix + KEY_DEX_TIME + secondaryNumber, NO_VALUE);
long lastModified = extractedFile.lastModified();
if ((expectedModTime != lastModified)
|| (expectedCrc != extractedFile.crc)) {
throw new IOException("Invalid extracted dex: " + extractedFile +
" (key \"" + prefsKeyPrefix + "\"), expected modification time: "
+ expectedModTime + ", modification time: "
+ lastModified + ", expected crc: "
+ expectedCrc + ", file crc: " + extractedFile.crc);
}
files.add(extractedFile);
} else {
throw new IOException("Missing extracted secondary dex file '" +
extractedFile.getPath() + "'");
}
}
return files;
}
/**
* Compare current archive and crc with values stored in {@link SharedPreferences}. Should be
* called only while owning the lock on {@link #LOCK_FILENAME}.
*/
private static boolean isModified(Context context, File archive, long currentCrc,
String prefsKeyPrefix) {
SharedPreferences prefs = getMultiDexPreferences(context);
return (prefs.getLong(prefsKeyPrefix + KEY_TIME_STAMP, NO_VALUE) != getTimeStamp(archive))
|| (prefs.getLong(prefsKeyPrefix + KEY_CRC, NO_VALUE) != currentCrc);
}
private static long getTimeStamp(File archive) {
long timeStamp = archive.lastModified();
if (timeStamp == NO_VALUE) {
// never return NO_VALUE
timeStamp--;
}
return timeStamp;
}
private static long getZipCrc(File archive) throws IOException {
long computedValue = ZipUtil.getZipCrc(archive);
if (computedValue == NO_VALUE) {
// never return NO_VALUE
computedValue--;
}
return computedValue;
}
private static List<ExtractedDex> performExtractions(File sourceApk, File dexDir)
throws IOException {
final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
// Ensure that whatever deletions happen in prepareDexDir only happen if the zip that
// contains a secondary dex file in there is not consistent with the latest apk. Otherwise,
// multi-process race conditions can cause a crash loop where one process deletes the zip
// while another had created it.
prepareDexDir(dexDir, extractedFilePrefix);
List<ExtractedDex> files = new ArrayList<ExtractedDex>();
final ZipFile apk = new ZipFile(sourceApk);
try {
int secondaryNumber = 2;
ZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);
while (dexFile != null) {
String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
ExtractedDex extractedFile = new ExtractedDex(dexDir, fileName);
files.add(extractedFile);
Log.i(TAG, "Extraction is needed for file " + extractedFile);
int numAttempts = 0;
boolean isExtractionSuccessful = false;
while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) {
numAttempts++;
// Create a zip file (extractedFile) containing only the secondary dex file
// (dexFile) from the apk.
extract(apk, dexFile, extractedFile, extractedFilePrefix);
// Read zip crc of extracted dex
try {
extractedFile.crc = getZipCrc(extractedFile);
isExtractionSuccessful = true;
} catch (IOException e) {
isExtractionSuccessful = false;
Log.w(TAG, "Failed to read crc from " + extractedFile.getAbsolutePath(), e);
}
// Log size and crc of the extracted zip file
Log.i(TAG, "Extraction " + (isExtractionSuccessful ? "succeeded" : "failed") +
" - length " + extractedFile.getAbsolutePath() + ": " +
extractedFile.length() + " - crc: " + extractedFile.crc);
if (!isExtractionSuccessful) {
// Delete the extracted file
extractedFile.delete();
if (extractedFile.exists()) {
Log.w(TAG, "Failed to delete corrupted secondary dex '" +
extractedFile.getPath() + "'");
}
}
}
if (!isExtractionSuccessful) {
throw new IOException("Could not create zip file " +
extractedFile.getAbsolutePath() + " for secondary dex (" +
secondaryNumber + ")");
}
secondaryNumber++;
dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);
}
} finally {
try {
apk.close();
} catch (IOException e) {
Log.w(TAG, "Failed to close resource", e);
}
}
return files;
}
/**
* Save {@link SharedPreferences}. Should be called only while owning the lock on
* {@link #LOCK_FILENAME}.
*/
private static void putStoredApkInfo(Context context, String keyPrefix, long timeStamp,
long crc, List<ExtractedDex> extractedDexes) {
SharedPreferences prefs = getMultiDexPreferences(context);
SharedPreferences.Editor edit = prefs.edit();
edit.putLong(keyPrefix + KEY_TIME_STAMP, timeStamp);
edit.putLong(keyPrefix + KEY_CRC, crc);
edit.putInt(keyPrefix + KEY_DEX_NUMBER, extractedDexes.size() + 1);
int extractedDexId = 2;
for (ExtractedDex dex : extractedDexes) {
edit.putLong(keyPrefix + KEY_DEX_CRC + extractedDexId, dex.crc);
edit.putLong(keyPrefix + KEY_DEX_TIME + extractedDexId, dex.lastModified());
extractedDexId++;
}
/* Use commit() and not apply() as advised by the doc because we need synchronous writing of
* the editor content and apply is doing an "asynchronous commit to disk".
*/
edit.commit();
}
/**
* Get the MuliDex {@link SharedPreferences} for the current application. Should be called only
* while owning the lock on {@link #LOCK_FILENAME}.
*/
private static SharedPreferences getMultiDexPreferences(Context context) {
return context.getSharedPreferences(PREFS_FILE,
Build.VERSION.SDK_INT < 11 /* Build.VERSION_CODES.HONEYCOMB */
? Context.MODE_PRIVATE
: Context.MODE_PRIVATE | 0x0004 /* Context.MODE_MULTI_PROCESS */);
}
/**
* This removes old files.
*/
private static void prepareDexDir(File dexDir, final String extractedFilePrefix) {
FileFilter filter = new FileFilter() {
@Override
public boolean accept(File pathname) {
String name = pathname.getName();
return !(name.startsWith(extractedFilePrefix)
|| name.equals(LOCK_FILENAME));
}
};
File[] files = dexDir.listFiles(filter);
if (files == null) {
Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ").");
return;
}
for (File oldFile : files) {
Log.i(TAG, "Trying to delete old file " + oldFile.getPath() + " of size " +
oldFile.length());
if (!oldFile.delete()) {
Log.w(TAG, "Failed to delete old file " + oldFile.getPath());
} else {
Log.i(TAG, "Deleted old file " + oldFile.getPath());
}
}
}
private static void extract(ZipFile apk, ZipEntry dexFile, File extractTo,
String extractedFilePrefix) throws IOException, FileNotFoundException {
InputStream in = apk.getInputStream(dexFile);
ZipOutputStream out = null;
// Temp files must not start with extractedFilePrefix to get cleaned up in prepareDexDir()
File tmp = File.createTempFile("tmp-" + extractedFilePrefix, EXTRACTED_SUFFIX,
extractTo.getParentFile());
Log.i(TAG, "Extracting " + tmp.getPath());
try {
out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tmp)));
try {
ZipEntry classesDex = new ZipEntry("classes.dex");
// keep zip entry time since it is the criteria used by Dalvik
classesDex.setTime(dexFile.getTime());
out.putNextEntry(classesDex);
byte[] buffer = new byte[BUFFER_SIZE];
int length = in.read(buffer);
while (length != -1) {
out.write(buffer, 0, length);
length = in.read(buffer);
}
out.closeEntry();
} finally {
out.close();
}
if (!tmp.setReadOnly()) {
throw new IOException("Failed to mark readonly \"" + tmp.getAbsolutePath() +
"\" (tmp of \"" + extractTo.getAbsolutePath() + "\")");
}
Log.i(TAG, "Renaming to " + extractTo.getPath());
if (!tmp.renameTo(extractTo)) {
throw new IOException("Failed to rename \"" + tmp.getAbsolutePath() +
"\" to \"" + extractTo.getAbsolutePath() + "\"");
}
} finally {
closeQuietly(in);
tmp.delete(); // return status ignored
}
}
/**
* Closes the given {@code Closeable}. Suppresses any IO exceptions.
*/
private static void closeQuietly(Closeable closeable) {
try {
closeable.close();
} catch (IOException e) {
Log.w(TAG, "Failed to close resource", e);
}
}
}

View file

@ -0,0 +1,125 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/* Apache Harmony HEADER because the code in this class comes mostly from ZipFile, ZipEntry and
* ZipConstants from android libcore.
*/
package android.support.multidex;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.zip.CRC32;
import java.util.zip.ZipException;
/**
* Tools to build a quick partial crc of zip files.
*/
final class ZipUtil {
static class CentralDirectory {
long offset;
long size;
}
/* redefine those constant here because of bug 13721174 preventing to compile using the
* constants defined in ZipFile */
private static final int ENDHDR = 22;
private static final int ENDSIG = 0x6054b50;
/**
* Size of reading buffers.
*/
private static final int BUFFER_SIZE = 0x4000;
/**
* Compute crc32 of the central directory of an apk. The central directory contains
* the crc32 of each entries in the zip so the computed result is considered valid for the whole
* zip file. Does not support zip64 nor multidisk but it should be OK for now since ZipFile does
* not either.
*/
static long getZipCrc(File apk) throws IOException {
RandomAccessFile raf = new RandomAccessFile(apk, "r");
try {
CentralDirectory dir = findCentralDirectory(raf);
return computeCrcOfCentralDir(raf, dir);
} finally {
raf.close();
}
}
/* Package visible for testing */
static CentralDirectory findCentralDirectory(RandomAccessFile raf) throws IOException,
ZipException {
long scanOffset = raf.length() - ENDHDR;
if (scanOffset < 0) {
throw new ZipException("File too short to be a zip file: " + raf.length());
}
long stopOffset = scanOffset - 0x10000 /* ".ZIP file comment"'s max length */;
if (stopOffset < 0) {
stopOffset = 0;
}
int endSig = Integer.reverseBytes(ENDSIG);
while (true) {
raf.seek(scanOffset);
if (raf.readInt() == endSig) {
break;
}
scanOffset--;
if (scanOffset < stopOffset) {
throw new ZipException("End Of Central Directory signature not found");
}
}
// Read the End Of Central Directory. ENDHDR includes the signature
// bytes,
// which we've already read.
// Pull out the information we need.
raf.skipBytes(2); // diskNumber
raf.skipBytes(2); // diskWithCentralDir
raf.skipBytes(2); // numEntries
raf.skipBytes(2); // totalNumEntries
CentralDirectory dir = new CentralDirectory();
dir.size = Integer.reverseBytes(raf.readInt()) & 0xFFFFFFFFL;
dir.offset = Integer.reverseBytes(raf.readInt()) & 0xFFFFFFFFL;
return dir;
}
/* Package visible for testing */
static long computeCrcOfCentralDir(RandomAccessFile raf, CentralDirectory dir)
throws IOException {
CRC32 crc = new CRC32();
long stillToRead = dir.size;
raf.seek(dir.offset);
int length = (int) Math.min(BUFFER_SIZE, stillToRead);
byte[] buffer = new byte[BUFFER_SIZE];
length = raf.read(buffer, 0, length);
while (length != -1) {
crc.update(buffer, 0, length);
stillToRead -= length;
if (stillToRead == 0) {
break;
}
length = (int) Math.min(BUFFER_SIZE, stillToRead);
length = raf.read(buffer, 0, length);
}
return crc.getValue();
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (C) 2014 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 android.support.multidex;
import org.junit.Assert;
import org.junit.Test;
/**
* Test for {@link MultiDex} class.
*/
public class MultiDexTest {
@Test
public void testVersionCheck() {
Assert.assertFalse(MultiDex.isVMMultidexCapable(null));
Assert.assertFalse(MultiDex.isVMMultidexCapable("-1.32.54"));
Assert.assertFalse(MultiDex.isVMMultidexCapable("1.32.54"));
Assert.assertFalse(MultiDex.isVMMultidexCapable("1.32"));
Assert.assertFalse(MultiDex.isVMMultidexCapable("2.0"));
Assert.assertFalse(MultiDex.isVMMultidexCapable("2.000.1254"));
Assert.assertTrue(MultiDex.isVMMultidexCapable("2.1.1254"));
Assert.assertTrue(MultiDex.isVMMultidexCapable("2.1"));
Assert.assertTrue(MultiDex.isVMMultidexCapable("2.2"));
Assert.assertTrue(MultiDex.isVMMultidexCapable("2.1.0000"));
Assert.assertTrue(MultiDex.isVMMultidexCapable("2.2.0000"));
Assert.assertTrue(MultiDex.isVMMultidexCapable("002.0001.0010"));
Assert.assertTrue(MultiDex.isVMMultidexCapable("3.0"));
Assert.assertTrue(MultiDex.isVMMultidexCapable("3.0.0"));
Assert.assertTrue(MultiDex.isVMMultidexCapable("3.0.1"));
Assert.assertTrue(MultiDex.isVMMultidexCapable("3.1.0"));
Assert.assertTrue(MultiDex.isVMMultidexCapable("03.1.132645"));
Assert.assertTrue(MultiDex.isVMMultidexCapable("03.2"));
}
}

View file

@ -0,0 +1,120 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/* Apache Harmony HEADER because the code in this class comes mostly from ZipFile, ZipEntry and
* ZipConstants from android libcore.
*/
package android.support.multidex;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
class ZipEntryReader {
static final Charset UTF_8 = Charset.forName("UTF-8");
/**
* General Purpose Bit Flags, Bit 0.
* If set, indicates that the file is encrypted.
*/
private static final int GPBF_ENCRYPTED_FLAG = 1 << 0;
/**
* Supported General Purpose Bit Flags Mask.
* Bit mask of bits not supported.
* Note: The only bit that we will enforce at this time
* is the encrypted bit. Although other bits are not supported,
* we must not enforce them as this could break some legitimate
* use cases (See http://b/8617715).
*/
private static final int GPBF_UNSUPPORTED_MASK = GPBF_ENCRYPTED_FLAG;
private static final long CENSIG = 0x2014b50;
static ZipEntry readEntry(ByteBuffer in) throws IOException {
int sig = in.getInt();
if (sig != CENSIG) {
throw new ZipException("Central Directory Entry not found");
}
in.position(8);
int gpbf = in.getShort() & 0xffff;
if ((gpbf & GPBF_UNSUPPORTED_MASK) != 0) {
throw new ZipException("Invalid General Purpose Bit Flag: " + gpbf);
}
int compressionMethod = in.getShort() & 0xffff;
int time = in.getShort() & 0xffff;
int modDate = in.getShort() & 0xffff;
// These are 32-bit values in the file, but 64-bit fields in this object.
long crc = ((long) in.getInt()) & 0xffffffffL;
long compressedSize = ((long) in.getInt()) & 0xffffffffL;
long size = ((long) in.getInt()) & 0xffffffffL;
int nameLength = in.getShort() & 0xffff;
int extraLength = in.getShort() & 0xffff;
int commentByteCount = in.getShort() & 0xffff;
// This is a 32-bit value in the file, but a 64-bit field in this object.
in.position(42);
long localHeaderRelOffset = ((long) in.getInt()) & 0xffffffffL;
byte[] nameBytes = new byte[nameLength];
in.get(nameBytes, 0, nameBytes.length);
String name = new String(nameBytes, 0, nameBytes.length, UTF_8);
ZipEntry entry = new ZipEntry(name);
entry.setMethod(compressionMethod);
entry.setTime(getTime(time, modDate));
entry.setCrc(crc);
entry.setCompressedSize(compressedSize);
entry.setSize(size);
// The RI has always assumed UTF-8. (If GPBF_UTF8_FLAG isn't set, the encoding is
// actually IBM-437.)
if (commentByteCount > 0) {
byte[] commentBytes = new byte[commentByteCount];
in.get(commentBytes, 0, commentByteCount);
entry.setComment(new String(commentBytes, 0, commentBytes.length, UTF_8));
}
if (extraLength > 0) {
byte[] extra = new byte[extraLength];
in.get(extra, 0, extraLength);
entry.setExtra(extra);
}
return entry;
}
private static long getTime(int time, int modDate) {
GregorianCalendar cal = new GregorianCalendar();
cal.set(Calendar.MILLISECOND, 0);
cal.set(1980 + ((modDate >> 9) & 0x7f), ((modDate >> 5) & 0xf) - 1,
modDate & 0x1f, (time >> 11) & 0x1f, (time >> 5) & 0x3f,
(time & 0x1f) << 1);
return cal.getTime().getTime();
}
}

View file

@ -0,0 +1,172 @@
/*
* Copyright (C) 2014 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 android.support.multidex;
import android.support.multidex.ZipUtil.CentralDirectory;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.EOFException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
/**
* Tests of ZipUtil class.
*
* The test assumes that ANDROID_BUILD_TOP environment variable is defined and point to the top of a
* built android tree. This is the case when the console used for running the tests is setup for
* android tree compilation.
*/
public class ZipUtilTest {
private static final File zipFile = new File(System.getenv("ANDROID_BUILD_TOP"),
"out/target/common/obj/JAVA_LIBRARIES/android-support-multidex_intermediates/javalib.jar");
@BeforeClass
public static void setupClass() throws ZipException, IOException {
// just verify the zip is valid
new ZipFile(zipFile).close();
}
@Test
public void testCrcDoNotCrash() throws IOException {
long crc =
ZipUtil.getZipCrc(zipFile);
System.out.println("crc is " + crc);
}
@Test
public void testCrcRange() throws IOException {
RandomAccessFile raf = new RandomAccessFile(zipFile, "r");
CentralDirectory dir = ZipUtil.findCentralDirectory(raf);
byte[] dirData = new byte[(int) dir.size];
int length = dirData.length;
int off = 0;
raf.seek(dir.offset);
while (length > 0) {
int read = raf.read(dirData, off, length);
if (length == -1) {
throw new EOFException();
}
length -= read;
off += read;
}
raf.close();
ByteBuffer buffer = ByteBuffer.wrap(dirData);
Map<String, ZipEntry> toCheck = new HashMap<String, ZipEntry>();
while (buffer.hasRemaining()) {
buffer = buffer.slice();
buffer.order(ByteOrder.LITTLE_ENDIAN);
ZipEntry entry = ZipEntryReader.readEntry(buffer);
toCheck.put(entry.getName(), entry);
}
ZipFile zip = new ZipFile(zipFile);
Assert.assertEquals(zip.size(), toCheck.size());
Enumeration<? extends ZipEntry> ref = zip.entries();
while (ref.hasMoreElements()) {
ZipEntry refEntry = ref.nextElement();
ZipEntry checkEntry = toCheck.get(refEntry.getName());
Assert.assertNotNull(checkEntry);
Assert.assertEquals(refEntry.getName(), checkEntry.getName());
Assert.assertEquals(refEntry.getComment(), checkEntry.getComment());
Assert.assertEquals(refEntry.getTime(), checkEntry.getTime());
Assert.assertEquals(refEntry.getCrc(), checkEntry.getCrc());
Assert.assertEquals(refEntry.getCompressedSize(), checkEntry.getCompressedSize());
Assert.assertEquals(refEntry.getSize(), checkEntry.getSize());
Assert.assertEquals(refEntry.getMethod(), checkEntry.getMethod());
Assert.assertArrayEquals(refEntry.getExtra(), checkEntry.getExtra());
}
zip.close();
}
@Test
public void testCrcValue() throws IOException {
ZipFile zip = new ZipFile(zipFile);
Enumeration<? extends ZipEntry> ref = zip.entries();
byte[] buffer = new byte[0x2000];
while (ref.hasMoreElements()) {
ZipEntry refEntry = ref.nextElement();
if (refEntry.getSize() > 0) {
File tmp = File.createTempFile("ZipUtilTest", ".fakezip");
InputStream in = zip.getInputStream(refEntry);
OutputStream out = new FileOutputStream(tmp);
int read = in.read(buffer);
while (read != -1) {
out.write(buffer, 0, read);
read = in.read(buffer);
}
in.close();
out.close();
RandomAccessFile raf = new RandomAccessFile(tmp, "r");
CentralDirectory dir = new CentralDirectory();
dir.offset = 0;
dir.size = raf.length();
long crc = ZipUtil.computeCrcOfCentralDir(raf, dir);
Assert.assertEquals(refEntry.getCrc(), crc);
raf.close();
tmp.delete();
}
}
zip.close();
}
@Test
public void testInvalidCrcValue() throws IOException {
ZipFile zip = new ZipFile(zipFile);
Enumeration<? extends ZipEntry> ref = zip.entries();
byte[] buffer = new byte[0x2000];
while (ref.hasMoreElements()) {
ZipEntry refEntry = ref.nextElement();
if (refEntry.getSize() > 0) {
File tmp = File.createTempFile("ZipUtilTest", ".fakezip");
InputStream in = zip.getInputStream(refEntry);
OutputStream out = new FileOutputStream(tmp);
int read = in.read(buffer);
while (read != -1) {
out.write(buffer, 0, read);
read = in.read(buffer);
}
in.close();
out.close();
RandomAccessFile raf = new RandomAccessFile(tmp, "r");
CentralDirectory dir = new CentralDirectory();
dir.offset = 0;
dir.size = raf.length() - 1;
long crc = ZipUtil.computeCrcOfCentralDir(raf, dir);
Assert.assertNotEquals(refEntry.getCrc(), crc);
raf.close();
tmp.delete();
}
}
zip.close();
}
}

View file

@ -0,0 +1,225 @@
/*
* Copyright (C) 2006 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 android.util;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.UnknownHostException;
/**
* Mock Log implementation for testing on non android host.
*/
public final class Log {
/**
* Priority constant for the println method; use Log.v.
*/
public static final int VERBOSE = 2;
/**
* Priority constant for the println method; use Log.d.
*/
public static final int DEBUG = 3;
/**
* Priority constant for the println method; use Log.i.
*/
public static final int INFO = 4;
/**
* Priority constant for the println method; use Log.w.
*/
public static final int WARN = 5;
/**
* Priority constant for the println method; use Log.e.
*/
public static final int ERROR = 6;
/**
* Priority constant for the println method.
*/
public static final int ASSERT = 7;
private Log() {
}
/**
* Send a {@link #VERBOSE} log message.
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
public static int v(String tag, String msg) {
return println(LOG_ID_MAIN, VERBOSE, tag, msg);
}
/**
* Send a {@link #VERBOSE} log message and log the exception.
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @param tr An exception to log
*/
public static int v(String tag, String msg, Throwable tr) {
return println(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
}
/**
* Send a {@link #DEBUG} log message.
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
public static int d(String tag, String msg) {
return println(LOG_ID_MAIN, DEBUG, tag, msg);
}
/**
* Send a {@link #DEBUG} log message and log the exception.
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @param tr An exception to log
*/
public static int d(String tag, String msg, Throwable tr) {
return println(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
}
/**
* Send an {@link #INFO} log message.
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
public static int i(String tag, String msg) {
return println(LOG_ID_MAIN, INFO, tag, msg);
}
/**
* Send a {@link #INFO} log message and log the exception.
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @param tr An exception to log
*/
public static int i(String tag, String msg, Throwable tr) {
return println(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr));
}
/**
* Send a {@link #WARN} log message.
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
public static int w(String tag, String msg) {
return println(LOG_ID_MAIN, WARN, tag, msg);
}
/**
* Send a {@link #WARN} log message and log the exception.
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @param tr An exception to log
*/
public static int w(String tag, String msg, Throwable tr) {
return println(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr));
}
/*
* Send a {@link #WARN} log message and log the exception.
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param tr An exception to log
*/
public static int w(String tag, Throwable tr) {
return println(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr));
}
/**
* Send an {@link #ERROR} log message.
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
public static int e(String tag, String msg) {
return println(LOG_ID_MAIN, ERROR, tag, msg);
}
/**
* Send a {@link #ERROR} log message and log the exception.
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @param tr An exception to log
*/
public static int e(String tag, String msg, Throwable tr) {
return println(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr));
}
/**
* Handy function to get a loggable stack trace from a Throwable
* @param tr An exception to log
*/
public static String getStackTraceString(Throwable tr) {
if (tr == null) {
return "";
}
// This is to reduce the amount of log spew that apps do in the non-error
// condition of the network being unavailable.
Throwable t = tr;
while (t != null) {
if (t instanceof UnknownHostException) {
return "";
}
t = t.getCause();
}
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
tr.printStackTrace(pw);
pw.flush();
return sw.toString();
}
/**
* Low-level logging call.
* @param priority The priority/type of this log message
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @return The number of bytes written.
*/
public static int println(int priority, String tag, String msg) {
return println(LOG_ID_MAIN, priority, tag, msg);
}
/** @hide */ public static final int LOG_ID_MAIN = 0;
/** @hide */ public static final int LOG_ID_RADIO = 1;
/** @hide */ public static final int LOG_ID_EVENTS = 2;
/** @hide */ public static final int LOG_ID_SYSTEM = 3;
/** @hide */ public static final int LOG_ID_CRASH = 4;
/** @hide */ @SuppressWarnings("unused")
public static int println(int bufID,
int priority, String tag, String msg) {
return 0;
}
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (C) 2017 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.
*/
include ':multidex'
project(':multidex').projectDir = new File(rootDir, 'library')
include ':multidex-instrumentation'
project(':multidex-instrumentation').projectDir = new File(rootDir, 'instrumentation')

View file

@ -0,0 +1,17 @@
/*
* Copyright (C) 2017 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.
*/
ext.multidexVersion = '1.0.2'