upload android base code part1
This commit is contained in:
parent
e02f198e2d
commit
0a1de6c4b3
48159 changed files with 9071466 additions and 0 deletions
19
android/frameworks/multidex/Android.mk
Normal file
19
android/frameworks/multidex/Android.mk
Normal 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
|
0
android/frameworks/multidex/MODULE_LICENSE_APACHE2
Normal file
0
android/frameworks/multidex/MODULE_LICENSE_APACHE2
Normal file
28
android/frameworks/multidex/NOTICE
Normal file
28
android/frameworks/multidex/NOTICE
Normal 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".
|
4
android/frameworks/multidex/OWNERS
Normal file
4
android/frameworks/multidex/OWNERS
Normal file
|
@ -0,0 +1,4 @@
|
|||
aqj@google.com
|
||||
benoitlamarche@google.com
|
||||
jplesot@google.com
|
||||
yroussel@google.com
|
6
android/frameworks/multidex/PREUPLOAD.cfg
Normal file
6
android/frameworks/multidex/PREUPLOAD.cfg
Normal 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
|
99
android/frameworks/multidex/build.gradle
Normal file
99
android/frameworks/multidex/build.gradle
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
BIN
android/frameworks/multidex/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
android/frameworks/multidex/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
android/frameworks/multidex/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
android/frameworks/multidex/gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
174
android/frameworks/multidex/gradlew
vendored
Executable 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 "$@"
|
34
android/frameworks/multidex/instrumentation/Android.mk
Normal file
34
android/frameworks/multidex/instrumentation/Android.mk
Normal 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)
|
|
@ -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>
|
10
android/frameworks/multidex/instrumentation/README
Normal file
10
android/frameworks/multidex/instrumentation/README
Normal 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.
|
||||
|
77
android/frameworks/multidex/instrumentation/build.gradle
Normal file
77
android/frameworks/multidex/instrumentation/build.gradle
Normal 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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
35
android/frameworks/multidex/library/Android.mk
Normal file
35
android/frameworks/multidex/library/Android.mk
Normal 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)
|
18
android/frameworks/multidex/library/AndroidManifest.xml
Normal file
18
android/frameworks/multidex/library/AndroidManifest.xml
Normal 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>
|
15
android/frameworks/multidex/library/README
Normal file
15
android/frameworks/multidex/library/README
Normal 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.
|
||||
|
73
android/frameworks/multidex/library/build.gradle
Normal file
73
android/frameworks/multidex/library/build.gradle
Normal 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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
21
android/frameworks/multidex/settings.gradle
Normal file
21
android/frameworks/multidex/settings.gradle
Normal 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')
|
17
android/frameworks/multidex/version.gradle
Normal file
17
android/frameworks/multidex/version.gradle
Normal 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'
|
Loading…
Add table
Add a link
Reference in a new issue