diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b006a86 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.idea/ +.gradle/ +build/ +repo/ +*.iml +.DS_Store +packer.properties diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..e69de29 diff --git a/deploy-local.sh b/deploy-local.sh new file mode 100755 index 0000000..e603269 --- /dev/null +++ b/deploy-local.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +echo "deploy plugin artifacts to local repo" +rm -rf /tmp/repo/ +./gradlew -PbuildNum=2013 -PRELEASE_REPOSITORY_URL=file:///tmp/repo -PSNAPSHOT_REPOSITORY_URL=file:///tmp/repo/ :plugin:clean :plugin:build :plugin:uploadArchives --stacktrace $1 diff --git a/deploy-remote.sh b/deploy-remote.sh new file mode 100755 index 0000000..c2bf01a --- /dev/null +++ b/deploy-remote.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +echo "build and deploy plugin artifacts to remote repo..." +./gradlew :plugin:clean :plugin:build :plugin:uploadArchives --stacktrace $1 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..c97a8bd Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..23465a3 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Dec 16 11:37:35 CST 2014 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-bin.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..91a7e26 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/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" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/plugin/build.gradle b/plugin/build.gradle new file mode 100644 index 0000000..9407886 --- /dev/null +++ b/plugin/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'groovy' + +buildscript { + repositories { + mavenCentral() + } +} + +repositories { + mavenCentral() +} + +dependencies { + compile gradleApi() + compile localGroovy() + compile 'com.android.tools.build:gradle:1.0.0' +} + +ext { + sourceCompatibility = 1.6 + targetCompatibility = 1.6 + +} + +apply from: 'https://github.com/mcxiaoke/gradle-mvn-push/raw/master/gradle-mvn-push.gradle' + diff --git a/plugin/gradle.properties b/plugin/gradle.properties new file mode 100644 index 0000000..84c5a2a --- /dev/null +++ b/plugin/gradle.properties @@ -0,0 +1,19 @@ +VERSION_NAME=0.9.0-SNAPSHOT + +POM_ARTIFACT_ID=packer +POM_PACKAGING=jar +POM_NAME=Android Market Packer + +GROUP=com.mcxiaoke.gradle + +POM_DESCRIPTION=Android Multi Market Packer Gradle Plugin +POM_URL=https://github.com/mcxiaoke/gradle-packer-plugin +POM_SCM_URL=https://github.com/mcxiaoke/gradle-packer-plugin.git +POM_SCM_CONNECTION=scm:git:https://github.com/mcxiaoke/gradle-packer-plugin.git +POM_SCM_DEV_CONNECTION=scm:git:https://github.com/mcxiaoke/gradle-packer-plugin.git +POM_LICENCE_NAME=Apache License, Version 2.0 +POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0 +POM_LICENCE_DIST=repo +POM_DEVELOPER_ID=mcxiaoke +POM_DEVELOPER_NAME=Xiaoke Zhang +POM_DEVELOPER_EMAIL=mail@mcxiaoke.com diff --git a/plugin/src/main/groovy/com/mcxiaoke/packer/AndroidPackerExtension.groovy b/plugin/src/main/groovy/com/mcxiaoke/packer/AndroidPackerExtension.groovy new file mode 100644 index 0000000..9ecc59e --- /dev/null +++ b/plugin/src/main/groovy/com/mcxiaoke/packer/AndroidPackerExtension.groovy @@ -0,0 +1,52 @@ +package com.mcxiaoke.packer + +import org.gradle.api.Project + +// Android Packer Plugin Extension +class AndroidPackerExtension { + + /** + * archive task output dir + */ + File archiveOutput + + /** + * file name template string + * + * Available vars: + * 1. projectName + * 2. appName + * 3. appPkg + * 4. buildType + * 5. flavorName + * 6. versionName + * 7. versionCode + * 8. buildTime + * + * default value: '${appPkg}-${flavorName}-${buildType}-v${versionName}-${versionCode}' + */ + String archiveNameFormat + + /** + * manifest meta-data key list + */ + List manifestMatcher + + /** + * support build number auto increment + * + * store in file: packer.properties + */ + boolean buildNumberAuto + /** + * auto build number build type list + */ + List buildNumberTypeMatcher + + + AndroidPackerExtension(Project project) { + archiveOutput = new File(project.rootProject.buildDir, "archives") + } + + +} diff --git a/plugin/src/main/groovy/com/mcxiaoke/packer/AndroidPackerPlugin.groovy b/plugin/src/main/groovy/com/mcxiaoke/packer/AndroidPackerPlugin.groovy new file mode 100644 index 0000000..539cbe2 --- /dev/null +++ b/plugin/src/main/groovy/com/mcxiaoke/packer/AndroidPackerPlugin.groovy @@ -0,0 +1,445 @@ +package com.mcxiaoke.packer + +import groovy.text.SimpleTemplateEngine +import groovy.xml.StreamingMarkupBuilder +import groovy.xml.XmlUtil +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.ProjectConfigurationException +import org.gradle.api.tasks.Copy + +import java.text.SimpleDateFormat + +// Android Multi Packer Plugin Source +class AndroidPackerPlugin implements Plugin { + static final PLUGIN_NAME = "packer" + static final P_OUTPUT = "output" + static final P_MARKET = "market" + static final P_BUILD_NUM = "buildNum" + + static final PROP_FILE = "packer.properties" + + static final String[] SUPPORTED_ANDROID_VERSIONS = ['0.14.', '1.0.'] + + Project project + Properties props + AndroidPackerExtension packerExt + + @Override + void apply(Project project) { + if (!hasAndroidPlugin(project)) { + throw new ProjectConfigurationException("the android plugin must be applied", null) + } + + this.project = project + this.props = loadProperties(project, PROP_FILE) + checkAndroidPlugin() + applyExtension() + applyConfigAndTasks() + } + + private void checkAndroidPlugin() { + def plugin = project.buildscript.configurations.classpath.dependencies.find { + it.group && it.group == 'com.android.tools.build' && it.name == 'gradle' + } + if (plugin && !isVersionSupported(plugin.version)) { + throw new IllegalStateException("the android plugin ${plugin.version} is not supported.") + } + } + + void applyExtension() { + // setup plugin and extension + project.configurations.create(PLUGIN_NAME).extendsFrom(project.configurations.compile) + this.packerExt = project.extensions.create(PLUGIN_NAME, AndroidPackerExtension, project) + } + + void applyConfigAndTasks() { + // checkBuildType() + // add markets must before evaluate + def hasMarkets = applyMarkets() + project.afterEvaluate { + def buildTypes = project.android.buildTypes + debug("applyConfigAndTasks() build types: ${buildTypes.collect { it.name }}") +// applySigningConfigs() + applyProperties() + addCleanTask() + project.android.applicationVariants.all { variant -> + checkSigningConfig(variant) + if (variant.buildType.name != "debug") { + if (hasMarkets) { + // modify manifest and add archive apk + // only when markets found and not debug + debug("markets found, add manifest and archive task.") + addArchiveTask(variant) + checkManifest(variant) + } else { + debug("markets not found, check version name.") + checkVersionName(variant) + } + } + } + } + } + +/** + * add task for clean apk archives + * @param project + */ + void addCleanTask() { + def cleanTask = project.tasks.create(name: 'cleanArchives') { + def output = packerExt.archiveOutput + debug("addCleanTask() ${output.absolutePath}") + cleanDir(output) + } + project.getTasksByName("clean", true)?.each { + it.dependsOn cleanTask + } + } + +/** + * add beta build type for debug + * @param project + * @return beta added + */ + boolean checkBuildType() { + def types = new HashSet(); + types.addAll(project.android.buildTypes.collect { it.name }) + debug('checkBuildType() default build types:' + types) + if (types.contains("beta")) { + debug('checkBuildType() beta found, ignore') + return true + } + debug('checkBuildType() create beta build type') + def betaType = project.android.buildTypes.create("beta", { + debuggable true + }) + def configs = project.android.signingConfigs + if (configs.findByName("release")) { + betaType.signingConfig = configs.release + } + + return true + } + +/** + * apply signing config for all build types + * @param project Project + */ + void applySigningConfigs() { + def configs = project.android.signingConfigs + if (configs.findByName("release")) { + debug("applySigningConfigs() release signingConfig found ") + project.android.buildTypes.each { + if (it.name != "debug") { + if (it.signingConfig == null) { + debug("applySigningConfigs() add signingConfig for type:" + it.name) + it.signingConfig = configs.release + } else { +// debug("applySigningConfigs() already defined, ignore type:" + it.name) + } + } + } + } else { + warn("release signingConfig not found ") + } + } + +/** + * parse markets file and apply to flavors + * @param project Project + * @return found markets file + */ + boolean applyMarkets() { + if (!project.hasProperty(P_MARKET)) { + debug("market property not found, ignore") + return false + } + + // check markets file exists + def marketsFilePath = project.property(P_MARKET).toString() + + File markets = new File(marketsFilePath); + if (!markets.exists()) { + debug("market file not exists, ignore") + return false + } + + debug("applyMarkets() file: ${marketsFilePath}") + def flavors = new HashSet(); + flavors.addAll(project.android.productFlavors.collect { it.name }) + debug("applyMarkets() default flavors:" + flavors) + + // add all markets + markets.eachLine { + def market = it.split('#')[0] + if (!flavors.contains(market)) { + project.android.productFlavors.create(market, {}) + debug("applyMarkets() new market: " + market) + } + } + return true + } + +/** + * check project properties and apply to extension + */ + void applyProperties() { + if (project.hasProperty(P_OUTPUT)) { + def dirName = project.property(P_OUTPUT) as String; + packerExt.archiveOutput = new File(project.rootProject.buildDir, dirName) + } + debug("applyProperties() output:" + packerExt.archiveOutput) + debug("applyProperties() manifest:" + packerExt.manifestMatcher) + } + + void checkSigningConfig(variant) { +// debug("checkSigningConfig() for ${variant.name}") + if (variant.buildType.signingConfig == null) { + debug("checkSigningConfig() signingConfig for ${variant.buildType.name} is null.") + } + } + +/** + * check and apply build number + * @param variant Variant + */ + void checkVersionName(variant) { + debug("checkVersionName() for variant:" + variant.name) + // check buildNum property first + if (project.hasProperty(P_BUILD_NUM)) { + variant.mergedFlavor.versionName += '.' + project.property(P_BUILD_NUM) + } else { + def auto = packerExt.buildNumberAuto + def patterns = packerExt.buildNumberTypeMatcher + def typeName = variant.buildType.name + if (auto && (patterns == null || patterns.contains(typeName))) { + // or apply auto increment build number + def newBuildNo = increaseBuildNumber(variant) + variant.mergedFlavor.versionName += "." + newBuildNo.toString(); + } + } + + debug("checkVersionName() versionName:${variant.mergedFlavor.versionName}") + } + + int increaseBuildNumber(variant) { + def typeName = variant.buildType.name + def versionName = variant.mergedFlavor.versionName + def key = "${versionName}.${typeName}.build" + def buildNo = props.getProperty(key, "0").toInteger() + 1 + //put new build number to props + props[key] = buildNo.toString() + //store property file + saveProperties(project, props, PROP_FILE) + return buildNo + } + +/** + * add archiveApk tasks + * @param variant current Variant + */ + void addArchiveTask(variant) { + def buildTypeName = variant.buildType.name + if (variant.buildType.signingConfig == null) { + warn("${variant.name}: signingConfig is null, ignore archive task.") + return + } + if (!variant.buildType.zipAlignEnabled) { + warn("${variant.name}: zipAlignEnabled==false, ignore archive task.") + return + } + debug("addArchiveTask() for ${variant.name}") + def apkName = buildApkName(variant) + def inputFile = variant.outputs[0].outputFile + def outputDir = packerExt.archiveOutput + debug("addArchiveTask() input: " + inputFile) + debug("addArchiveTask() output: " + outputDir) + def archiveTask = project.tasks.create(name: "archiveApk${variant.name.capitalize()}", + type: Copy) { + description = "copy apk and rename to ${apkName}" + from inputFile.absolutePath + into outputDir.absolutePath + rename { filename -> + filename.replace inputFile.name, apkName + } + + dependsOn variant.assemble + } + + debug("addArchiveTask() new task:" + archiveTask.name) + + if (!variant.name.equals(buildTypeName)) { + def taskName = "archiveApk${buildTypeName.capitalize()}" + def parentTask = project.tasks.findByName(taskName) + if (parentTask == null) { + debug("addArchiveTask() create parent task " + taskName) + parentTask = project.tasks.create(taskName) + parentTask.description = 'copy all apk archives to destination output dir' + } + parentTask.dependsOn archiveTask + } + } + +/** + * build human readable apk name + * @param variant Variant + * @return final apk name + */ + String buildApkName(variant) { + def dateFormat = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss') + def buildTime = dateFormat.format(new Date()) + .replaceAll('\\.', '-') + .replaceAll(':', '-') + .replaceAll(' ', '-') + def nameMap = [ + 'appName' : project.name, + 'projectName': project.rootProject.name, + 'flavorName' : variant.flavorName, + 'buildType' : variant.buildType.name, + 'versionName': variant.versionName, + 'versionCode': variant.versionCode, + 'appPkg' : variant.applicationId, + 'buildTime' : buildTime + ] + + def defaultTemplate = '${appPkg}-${flavorName}-${buildType}-v${versionName}-${versionCode}' + def engine = new SimpleTemplateEngine() + def template = packerExt.archiveNameFormat == null ? defaultTemplate : packerExt.archiveNameFormat + def fileName = engine.createTemplate(template).make(nameMap).toString(); + def apkName = fileName + '.apk' + debug("buildApkName() final apkName: " + apkName) + return apkName + } + +/** + * check and add process manifest actions + * @param variant Variant + */ + void checkManifest(variant) { + if (!variant.productFlavors) { + warn("${variant.name}: manifest task, no flavors found, ignore.") + return + } + if (!variant.outputs) { + warn("${variant.name}: manifest task, no outputs found, ignore.") + return + } + def processManifestTask = variant.outputs[0].processManifest + processManifestTask.doLast { task -> + processManifest(task, variant) + } + } + +/** + * parse and modify manifest file + * @param task process manifest task + * @param variant Variant + */ + void processManifest(task, variant) { + def File manifestFile = task.manifestOutputFile + def typeName = variant.buildType.name + def flavorName = variant.productFlavors[0].name + debug("processManifest() flavor: ${flavorName} type:${typeName}") + def root = new XmlSlurper().parse(manifestFile) + .declareNamespace(android: "http://schemas.android.com/apk/res/android") + debug("${variant.name}: manifest matcher:${packerExt.manifestMatcher}") + packerExt.manifestMatcher?.each { String pattern -> +// debug("processManifest() check pattern:${pattern}"); + def metadata = root.application.'meta-data' + def found = metadata.find { mt -> pattern == mt.'@android:name'.toString() } + if (found.size() > 0) { + warn("${variant.name}: manifest meta-data ${pattern} found, modify it") + found.replaceNode { + 'meta-data'('android:name': found."@android:name", 'android:value': flavorName) {} + } + } else { + warn("${variant.name}: manifest meta-data ${pattern} not found, add it.") + root.application.appendNode { + 'meta-data'('android:name': pattern, 'android:value': flavorName) {} + } + } + } + + serializeXml(root, manifestFile) +// debug("processManifest() final manifest:${manifestFile.text}") + } + +/** + * check android plugin applied + * @param project Project + * @return plugin applied + */ + static boolean hasAndroidPlugin(Project project) { + return project.plugins.hasPlugin("com.android.application") + } + +/** + * print debug messages + * @param msg msg + * @param vars vars + */ + void debug(String msg, Object... vars) { +// println msg + project.logger.debug(msg, vars) + } + + void warn(String msg, Object... vars) { + project.logger.warn(msg, vars) + } + + static void saveProperties(Project project, Properties props, String fileName) { + props.store(new FileWriter(project.file(fileName)), null) + } + + static Properties loadProperties(Project project, String fileName) { + def props = new Properties() + def file = project.file(fileName) + if (!file.exists()) { + file.createNewFile() + } else { + props.load(new FileReader(file)) + } + return props + } + + static boolean getGitSha() { + return 'git rev-parse --short HEAD'.execute().text.trim() + } + + static boolean isVersionSupported(String version) { + for (supportedVersion in SUPPORTED_ANDROID_VERSIONS) { + if (version.startsWith(supportedVersion)) { + return true + } + } + + return false + } + +/** + * write xml to file + * @param xml xml + * @param file file + */ + static void serializeXml(xml, file) { + XmlUtil.serialize(new StreamingMarkupBuilder().bind { mkp.yield xml }, + new FileWriter(file)) + } + +/** + * delete all files in dir + * @param dir + */ + static void cleanDir(File dir) { + if (dir && dir.listFiles()) { + dir.listFiles().sort().each { File file -> + if (file.isFile()) { + file.delete() + } else { + file.deleteDir() + } + } + } + } + +} diff --git a/plugin/src/main/resources/META-INF/gradle-plugins/packer.properties b/plugin/src/main/resources/META-INF/gradle-plugins/packer.properties new file mode 100644 index 0000000..32f3190 --- /dev/null +++ b/plugin/src/main/resources/META-INF/gradle-plugins/packer.properties @@ -0,0 +1 @@ +implementation-class=com.mcxiaoke.packer.AndroidPackerPlugin diff --git a/plugin/src/test/groovy/com/mcxiaoke/packer/AndroidPackerPluginTest.groovy b/plugin/src/test/groovy/com/mcxiaoke/packer/AndroidPackerPluginTest.groovy new file mode 100644 index 0000000..83d9057 --- /dev/null +++ b/plugin/src/test/groovy/com/mcxiaoke/packer/AndroidPackerPluginTest.groovy @@ -0,0 +1,109 @@ +package com.mcxiaoke.packer + +import org.junit.Assert +import org.gradle.api.Project +import org.gradle.api.ProjectConfigurationException +import org.gradle.testfixtures.ProjectBuilder +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException + +class AndroidPackerPluginTest { + @Rule + public ExpectedException thrown = ExpectedException.none() + + @Before + public void setUp() { + } + + @Test + public void testSupportedAndroidVersion() { + Assert.assertFalse(AndroidPackerPlugin.isVersionSupported('0.11.+')) + Assert.assertFalse(AndroidPackerPlugin.isVersionSupported('0.13.1')) + Assert.assertTrue(AndroidPackerPlugin.isVersionSupported('0.14.+')) + Assert.assertTrue(AndroidPackerPlugin.isVersionSupported('0.14.2')) + Assert.assertTrue(AndroidPackerPlugin.isVersionSupported('1.0.0-rc1')) + Assert.assertTrue(AndroidPackerPlugin.isVersionSupported('1.0.0')) + } + + @Test(expected = ProjectConfigurationException.class) + public void testWithoutAndroidPlugin() { + Project project = ProjectBuilder.builder().build() + configBuildScript(project, 'com.android.tools.build:gradle:1.0.0') + new AndroidPackerPlugin().apply(project) + } + + @Test + public void testWithAndroidPlugin() { + Project project = ProjectBuilder.builder().build() + configBuildScript(project, 'com.android.tools.build:gradle:1.0.0') + project.apply plugin: 'com.android.application' + new AndroidPackerPlugin().apply(project) + } + + @Test(expected = IllegalStateException.class) + public void testUnSupportedAndroidPlugin() { + Project project = ProjectBuilder.builder().build() + configBuildScript(project, 'com.android.tools.build:gradle:0.13.1') + project.apply plugin: 'com.android.application' + new AndroidPackerPlugin().apply(project) + project.evaluate() + } + +// @Test +// public void testVariantsAndBuildTypes() { +// Project project = createProject() +// project.evaluate() +// def variants = project.android.applicationVariants +// def variantNames = variants.collect { it.name }.join(' ') +// Assert.assertEquals(variants.size(), 3) +// Assert.assertTrue(variantNames.contains('beta')) +// +// def variantBeta = variants.find { it.buildType.name == 'beta' } +// Assert.assertEquals(variantBeta.buildType.name, 'beta') +// } + + private Project createProject() { + Project project = ProjectBuilder.builder().withName('plugin-test').build() +// configBuildScript(project, 'com.android.tools.build:gradle:1.0.0') + project.apply plugin: 'com.android.application' + project.apply plugin: 'packer' + project.android { + compileSdkVersion 21 + buildToolsVersion "21.1.1" + + defaultConfig { + minSdkVersion 14 + targetSdkVersion 21 + versionCode 1 + versionName "1.0.0" + applicationId 'com.mcxiaoke.packer.test' + } + + signingConfigs { + release { + storeFile project.file("android.keystore") + storePassword "android" + keyAlias "android" + keyPassword "android" + } + + } + } + return project + } + + static void configBuildScript(Project project, String androidVersion) { + project.buildscript { + repositories { + maven { url "/tmp/repo/" } + mavenCentral() + } + dependencies { + classpath androidVersion + } + } + } + +} diff --git a/plugin/src/test/market.txt b/plugin/src/test/market.txt new file mode 100644 index 0000000..c8019e8 --- /dev/null +++ b/plugin/src/test/market.txt @@ -0,0 +1,2 @@ +Douban +Google_Market diff --git a/sample/.gitignore b/sample/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/sample/.gitignore @@ -0,0 +1 @@ +/build diff --git a/sample/a.xml b/sample/a.xml new file mode 100644 index 0000000..e2a1d90 --- /dev/null +++ b/sample/a.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/sample/android.keystore b/sample/android.keystore new file mode 100644 index 0000000..e145fb8 Binary files /dev/null and b/sample/android.keystore differ diff --git a/sample/build.gradle b/sample/build.gradle new file mode 100644 index 0000000..9315e4c --- /dev/null +++ b/sample/build.gradle @@ -0,0 +1,103 @@ +buildscript { + repositories { + maven { url '/tmp/repo' } + // maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.0.0' + classpath 'com.mcxiaoke.gradle:packer:0.9.0-SNAPSHOT' + } +} + +repositories { + mavenCentral() +} + +apply plugin: 'com.android.application' +apply plugin: 'packer' + +packer { + // type:file + archiveOutput = file(new File(project.rootProject.buildDir.path, "apks")) + // type:String + manifestMatcher = ['UMENG_CHANNEL','Promotion_Market'] + // type:groovy String template + // archiveNameFormat = '' + buildNumberAuto = true + buildNumberTypeMatcher = ['release'] + +} + +android { + compileSdkVersion 21 + buildToolsVersion "21.1.1" + + defaultConfig { + applicationId "com.mcxiaoke.packer.sample" + minSdkVersion 15 + targetSdkVersion 21 + versionCode 12345 + versionName "1.2.3" + } + + signingConfigs { + release { + storeFile file("android.keystore") + storePassword "android" + keyAlias "android" + keyPassword "android" + } + } + + buildTypes { + release { + signingConfig signingConfigs.release + minifyEnabled false + } + + //beta { + // minifyEnabled false + // debuggable true + //} + + someType { + signingConfig signingConfigs.release + minifyEnabled false + debuggable true + } + + } + + productFlavors { + //hello { + // versionName "helloworld" + //} + + //PackerTest { + // versionName "1.0-packer-test" + //} + } + + lintOptions { + abortOnError false + } + + tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:support-v4:21.0.2' + compile 'com.android.support:appcompat-v7:21.0.2' + compile 'com.jakewharton:butterknife:6.0.0' + compile('com.mcxiaoke.next:core:1.0.4@aar') { + exclude group: 'com.android.support', module: 'support-v4' + } + compile('com.mcxiaoke.next:http:1.0.4@aar') + compile('com.mcxiaoke.next:ui:1.0.4@aar') { + exclude group: 'com.android.support', module: 'support-v4' + } +} diff --git a/sample/custom.txt b/sample/custom.txt new file mode 100644 index 0000000..a7ebc91 --- /dev/null +++ b/sample/custom.txt @@ -0,0 +1,2 @@ +Custom_Market#for test +Wahaha#for test diff --git a/sample/local.properties b/sample/local.properties new file mode 100644 index 0000000..1003030 --- /dev/null +++ b/sample/local.properties @@ -0,0 +1,11 @@ +## This file is automatically generated by Android Studio. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +#Thu Dec 11 11:24:17 CST 2014 +sdk.dir=/Users/mcxiaoke/develop/android-sdk-macosx diff --git a/sample/markets.txt b/sample/markets.txt new file mode 100644 index 0000000..2ed1e81 --- /dev/null +++ b/sample/markets.txt @@ -0,0 +1,2 @@ +App_Market#a test market +PluginTest#plugin test diff --git a/sample/packer.properties b/sample/packer.properties new file mode 100644 index 0000000..1ba096f --- /dev/null +++ b/sample/packer.properties @@ -0,0 +1,2 @@ +#Tue Dec 16 16:48:26 CST 2014 +1.2.3.release.build=7 diff --git a/sample/src/androidTest/java/com/mcxiaoke/packer/samples/ApplicationTest.java b/sample/src/androidTest/java/com/mcxiaoke/packer/samples/ApplicationTest.java new file mode 100644 index 0000000..b2d69ec --- /dev/null +++ b/sample/src/androidTest/java/com/mcxiaoke/packer/samples/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.mcxiaoke.packer.samples; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml new file mode 100644 index 0000000..13550c6 --- /dev/null +++ b/sample/src/main/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample/src/main/gen/com/mcxiaoke/mpp/sample/BuildConfig.java b/sample/src/main/gen/com/mcxiaoke/mpp/sample/BuildConfig.java new file mode 100644 index 0000000..e6be963 --- /dev/null +++ b/sample/src/main/gen/com/mcxiaoke/mpp/sample/BuildConfig.java @@ -0,0 +1,8 @@ +/*___Generated_by_IDEA___*/ + +package com.mcxiaoke.mpp.sample; + +/* This stub is only used by the IDE. It is NOT the BuildConfig class actually packed into the APK */ +public final class BuildConfig { + public final static boolean DEBUG = Boolean.parseBoolean(null); +} \ No newline at end of file diff --git a/sample/src/main/gen/com/mcxiaoke/mpp/sample/Manifest.java b/sample/src/main/gen/com/mcxiaoke/mpp/sample/Manifest.java new file mode 100644 index 0000000..7397486 --- /dev/null +++ b/sample/src/main/gen/com/mcxiaoke/mpp/sample/Manifest.java @@ -0,0 +1,7 @@ +/*___Generated_by_IDEA___*/ + +package com.mcxiaoke.mpp.sample; + +/* This stub is only used by the IDE. It is NOT the Manifest class actually packed into the APK */ +public final class Manifest { +} \ No newline at end of file diff --git a/sample/src/main/gen/com/mcxiaoke/mpp/sample/R.java b/sample/src/main/gen/com/mcxiaoke/mpp/sample/R.java new file mode 100644 index 0000000..2417ea3 --- /dev/null +++ b/sample/src/main/gen/com/mcxiaoke/mpp/sample/R.java @@ -0,0 +1,7 @@ +/*___Generated_by_IDEA___*/ + +package com.mcxiaoke.mpp.sample; + +/* This stub is only used by the IDE. It is NOT the R class actually packed into the APK */ +public final class R { +} \ No newline at end of file diff --git a/sample/src/main/java/com/mcxiaoke/packer/samples/MainActivity.java b/sample/src/main/java/com/mcxiaoke/packer/samples/MainActivity.java new file mode 100644 index 0000000..b4315a2 --- /dev/null +++ b/sample/src/main/java/com/mcxiaoke/packer/samples/MainActivity.java @@ -0,0 +1,244 @@ +package com.mcxiaoke.packer.samples; + +import android.annotation.SuppressLint; +import android.app.ActivityManager; +import android.app.ActivityManager.MemoryInfo; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.Point; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Build; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.support.v7.app.ActionBarActivity; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.widget.TextView; +import butterknife.ButterKnife; +import butterknife.InjectView; +import com.mcxiaoke.next.utils.AndroidUtils; +import com.mcxiaoke.next.utils.LogUtils; +import com.mcxiaoke.next.utils.StringUtils; +import com.mcxiaoke.packer.sample.BuildConfig; +import com.mcxiaoke.packer.sample.R; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Set; + + +public class MainActivity extends ActionBarActivity { + private static final String TAG = MainActivity.class.getSimpleName(); + + @InjectView(R.id.container) + ViewGroup mContainer; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.act_main); + ButterKnife.inject(this); + addAppInfoSection(); + addMetaDataSection(); + addBuildConfigSection(); + addNetworkInfoSection(); + addDeviceInfoSection(); + addBuildPropsSection(); + + } + + private void addAppInfoSection() { + try { + final PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0); + final ApplicationInfo info = pi.applicationInfo; + StringBuilder builder = new StringBuilder(); + builder.append("[AppInfo]\n"); + builder.append("Name: ").append(getString(info.labelRes)).append("\n"); + builder.append("Package: ").append(BuildConfig.APPLICATION_ID).append("\n"); + builder.append("VersionCode: ").append(BuildConfig.VERSION_CODE).append("\n"); + builder.append("VersionName: ").append(BuildConfig.VERSION_NAME).append("\n"); + builder.append("ProcessName: ").append(info.processName).append("\n"); + builder.append("SourceDir: ").append(info.sourceDir).append("\n"); + builder.append("DataDir: ").append(info.dataDir).append("\n"); + builder.append("Signature:\n"); + builder.append(AndroidUtils.getSignatureInfo(this)).append("\n"); + builder.append("\n"); + addSection(builder.toString()); + } catch (Exception e) { + } + + + } + + private void addMetaDataSection() { + final PackageManager pm = getPackageManager(); + final String packageName = getPackageName(); + try { + final ApplicationInfo info = pm.getApplicationInfo(packageName, + PackageManager.GET_SIGNATURES | PackageManager.GET_META_DATA); + final Bundle bundle = info.metaData; + final StringBuilder builder = new StringBuilder(); + builder.append("[MetaData]\n"); + if (bundle != null) { + final Set keySet = bundle.keySet(); + for (final String key : keySet) { + builder.append(key).append("=").append(bundle.get(key)).append("\n"); + } + } + addSection(builder.toString()); + } catch (NameNotFoundException e) { + e.printStackTrace(); + } + } + + private void addNetworkInfoSection() { + StringBuilder builder = new StringBuilder(); + builder.append("[Network]\n"); + ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo info = cm.getActiveNetworkInfo(); + if (info != null) { + builder.append(info); + } + builder.append("\n\n"); + addSection(builder.toString()); + } + + @SuppressLint("NewApi") + private void addDeviceInfoSection() { + StringBuilder builder = new StringBuilder(); + builder.append("[Device]\n"); + + ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + final MemoryInfo memoryInfo = new MemoryInfo(); + am.getMemoryInfo(memoryInfo); + if (AndroidUtils.hasJellyBean()) { + builder.append("Mem Total: ").append(StringUtils.getHumanReadableByteCount(memoryInfo.totalMem)).append("\n"); + } + builder.append("Mem Free: ").append(StringUtils.getHumanReadableByteCount(memoryInfo.availMem)).append("\n"); + builder.append("Mem Heap: ").append(am.getMemoryClass()).append("M\n"); + builder.append("Mem Low: ").append(memoryInfo.lowMemory).append("\n"); + Display display = getWindowManager().getDefaultDisplay(); + DisplayMetrics dm = new DisplayMetrics(); + //DisplayMetrics dm = getResources().getDisplayMetrics(); + display.getMetrics(dm); + + int statusBarHeightDp = ViewUtils.getStatusBarHeightInDp(this); + int systemBarHeightDp = ViewUtils.getSystemBarHeightInDp(this); + int statusBarHeight = ViewUtils.getStatusBarHeight(this); + int systemBarHeight = ViewUtils.getSystemBarHeight(this); + Point point = getScreenRawSize(display); + builder.append("statusBarHeightDp: ").append(statusBarHeightDp).append("\n"); + builder.append("systemBarHeightDp: ").append(systemBarHeightDp).append("\n"); + builder.append("statusBarHeightPx: ").append(statusBarHeight).append("\n"); + builder.append("systemBarHeightPx: ").append(systemBarHeight).append("\n"); + builder.append("screenWidth: ").append(point.x).append("\n"); + builder.append("screenHeight: ").append(point.y).append("\n"); + builder.append("WindowWidth: ").append(dm.widthPixels).append("\n"); + builder.append("WindowHeight: ").append(dm.heightPixels).append("\n"); + builder.append(toString2(dm)); + builder.append("\n"); + addSection(builder.toString()); + } + + private void addBuildConfigSection() { + StringBuilder builder = new StringBuilder(); + builder.append("[BuildConfig]\n"); + builder.append(toString(BuildConfig.class)); + builder.append("\n"); + addSection(builder.toString()); + } + + private void addBuildPropsSection() { + StringBuilder builder = new StringBuilder(); + builder.append("[System]\n"); + builder.append(toString(Build.VERSION.class)); + builder.append(toString(Build.class)); + builder.append("\n"); + addSection(builder.toString()); + } + + private LayoutParams TEXT_VIEW_LP = new LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT); + + private void addSection(CharSequence text) { + TextView tv = new TextView(this); + tv.setLayoutParams(TEXT_VIEW_LP); + tv.setText(text); + tv.setTextIsSelectable(true); + mContainer.addView(tv); + } + + + @SuppressLint("NewApi") + public static Point getScreenRawSize(Display display) { + if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + Point outPoint = new Point(); + DisplayMetrics metrics = new DisplayMetrics(); + display.getRealMetrics(metrics); + outPoint.x = metrics.widthPixels; + outPoint.y = metrics.heightPixels; + return outPoint; + } else { + Point outPoint = new Point(); + Method mGetRawH; + try { + mGetRawH = Display.class.getMethod("getRawHeight"); + Method mGetRawW = Display.class.getMethod("getRawWidth"); + outPoint.x = (Integer) mGetRawW.invoke(display); + outPoint.y = (Integer) mGetRawH.invoke(display); + return outPoint; + } catch (Throwable e) { + return new Point(0, 0); + } + } + } + + public static String toString(Class clazz) { + StringBuilder builder = new StringBuilder(); + final String newLine = System.getProperty("line.separator"); + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + String fieldName = field.getName(); + if (Modifier.isStatic(field.getModifiers())) { + LogUtils.v(TAG, "filed:" + fieldName); + try { + Object fieldValue = field.get(null); + builder.append(fieldName).append(": ").append(fieldValue).append(newLine); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + return builder.toString(); + } + + public static String toString2(Object object) { + Class clazz = object.getClass(); + StringBuilder builder = new StringBuilder(); + final String newLine = System.getProperty("line.separator"); + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + String fieldName = field.getName(); + if (!Modifier.isStatic(field.getModifiers())) { + LogUtils.v(TAG, "filed:" + fieldName); + try { + Object fieldValue = field.get(object); + builder.append(fieldName).append(": ").append(fieldValue).append(newLine); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + return builder.toString(); + } + +} diff --git a/sample/src/main/java/com/mcxiaoke/packer/samples/ViewUtils.java b/sample/src/main/java/com/mcxiaoke/packer/samples/ViewUtils.java new file mode 100644 index 0000000..6777a90 --- /dev/null +++ b/sample/src/main/java/com/mcxiaoke/packer/samples/ViewUtils.java @@ -0,0 +1,185 @@ +package com.mcxiaoke.packer.samples; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.graphics.Point; +import android.os.Build; +import android.os.Build.VERSION_CODES; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.Display; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; + +import java.lang.reflect.Method; + +/** + * User: mcxiaoke + * Date: 14-3-26 + * Time: 16:08 + */ +public class ViewUtils { + + public static ProgressBar createProgress(Context context) { + ProgressBar p = new ProgressBar(context); + p.setIndeterminate(true); + RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(40, 40); + lp.addRule(RelativeLayout.CENTER_IN_PARENT); + p.setLayoutParams(lp); + return p; + } + + // This intro hides the system bars. + @TargetApi(VERSION_CODES.KITKAT) + public static void hideSystemUI(Activity activity) { + // Set the IMMERSIVE flag. + // Set the content to appear under the system bars so that the content + // doesn't resize when the system bars hideSelf and show. + View decorView = activity.getWindow().getDecorView(); + decorView.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hideSelf nav bar + | View.SYSTEM_UI_FLAG_FULLSCREEN // hideSelf status bar + | View.SYSTEM_UI_FLAG_IMMERSIVE + ); + } + + // This intro shows the system bars. It does this by removing all the flags +// except for the ones that make the content appear under the system bars. + @TargetApi(VERSION_CODES.KITKAT) + public static void showSystemUI(Activity activity) { + View decorView = activity.getWindow().getDecorView(); + decorView.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + ); + } + + /** + * 23 * Returns true if view's layout direction is right-to-left. + * 24 * + * 25 * @param view the View whose layout is being considered + * 26 + */ + @SuppressLint("NewApi") + public static boolean isLayoutRtl(View view) { + if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + return view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + } else { + // All layouts are LTR before JB MR1. + return false; + } + } + + @SuppressLint("NewApi") + public static Point getScreenRawSize(Display display) { + if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + Point outPoint = new Point(); + DisplayMetrics metrics = new DisplayMetrics(); + display.getRealMetrics(metrics); + outPoint.x = metrics.widthPixels; + outPoint.y = metrics.heightPixels; + return outPoint; + } else { + Point outPoint = new Point(); + Method mGetRawH; + try { + mGetRawH = Display.class.getMethod("getRawHeight"); + Method mGetRawW = Display.class.getMethod("getRawWidth"); + outPoint.x = (Integer) mGetRawW.invoke(display); + outPoint.y = (Integer) mGetRawH.invoke(display); + return outPoint; + } catch (Throwable e) { + return new Point(0, 0); + } + } + } + + public static int getActionBarHeightInDp(Context context) { + int actionBarHeight = 0; + TypedValue tv = new TypedValue(); + final DisplayMetrics dm = context.getResources().getDisplayMetrics(); + if (Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, + true)) + actionBarHeight = (int) TypedValue.complexToFloat(tv.data); + } else { + tv.data = 48; + actionBarHeight = (int) TypedValue.complexToFloat(tv.data); + } + return actionBarHeight; + } + + public static int getActionBarHeight(Context context) { + int actionBarHeight = 0; + TypedValue tv = new TypedValue(); + final DisplayMetrics dm = context.getResources().getDisplayMetrics(); + if (Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { + if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, + true)) + actionBarHeight = TypedValue.complexToDimensionPixelSize( + tv.data, dm); + } else { + tv.data = 48; + actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, + dm); + } + return actionBarHeight; + } + + public static int getStatusBarHeight(Context context) { + int result = 0; + int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + + result = context.getResources().getDimensionPixelSize(resourceId); + } + return result; + } + + public static int getSystemBarHeight(Context context) { + int result = 0; + int resourceId = context.getResources().getIdentifier("system_bar_height", "dimen", "android"); + + if (resourceId > 0) { + result = context.getResources().getDimensionPixelSize(resourceId); + } + return result; + } + + public static int getStatusBarHeightInDp(Context context) { + int result = 0; + int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + result = getResourceValue(context, resourceId); + } + return result; + } + + public static int getSystemBarHeightInDp(Context context) { + int result = 0; + int resourceId = context.getResources().getIdentifier("system_bar_height", "dimen", "android"); + if (resourceId > 0) { + result = getResourceValue(context, resourceId); + } + return result; + } + + // temp variable + private static TypedValue mTmpValue = new TypedValue(); + + /** + * 获取资源中的数值,没有经过转换,比如dp,sp等 + */ + public static int getResourceValue(Context context, int resId) { + TypedValue value = mTmpValue; + context.getResources().getValue(resId, value, true); + return (int) TypedValue.complexToFloat(value.data); + } +} diff --git a/sample/src/main/res/drawable-hdpi/drawer_shadow.9.png b/sample/src/main/res/drawable-hdpi/drawer_shadow.9.png new file mode 100644 index 0000000..236bff5 Binary files /dev/null and b/sample/src/main/res/drawable-hdpi/drawer_shadow.9.png differ diff --git a/sample/src/main/res/drawable-hdpi/ic_drawer.png b/sample/src/main/res/drawable-hdpi/ic_drawer.png new file mode 100644 index 0000000..c59f601 Binary files /dev/null and b/sample/src/main/res/drawable-hdpi/ic_drawer.png differ diff --git a/sample/src/main/res/drawable-hdpi/ic_launcher.png b/sample/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..96a442e Binary files /dev/null and b/sample/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/sample/src/main/res/drawable-mdpi/drawer_shadow.9.png b/sample/src/main/res/drawable-mdpi/drawer_shadow.9.png new file mode 100644 index 0000000..ffe3a28 Binary files /dev/null and b/sample/src/main/res/drawable-mdpi/drawer_shadow.9.png differ diff --git a/sample/src/main/res/drawable-mdpi/ic_drawer.png b/sample/src/main/res/drawable-mdpi/ic_drawer.png new file mode 100644 index 0000000..1ed2c56 Binary files /dev/null and b/sample/src/main/res/drawable-mdpi/ic_drawer.png differ diff --git a/sample/src/main/res/drawable-mdpi/ic_launcher.png b/sample/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..359047d Binary files /dev/null and b/sample/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/sample/src/main/res/drawable-xhdpi/drawer_shadow.9.png b/sample/src/main/res/drawable-xhdpi/drawer_shadow.9.png new file mode 100644 index 0000000..fabe9d9 Binary files /dev/null and b/sample/src/main/res/drawable-xhdpi/drawer_shadow.9.png differ diff --git a/sample/src/main/res/drawable-xhdpi/ic_drawer.png b/sample/src/main/res/drawable-xhdpi/ic_drawer.png new file mode 100644 index 0000000..a5fa74d Binary files /dev/null and b/sample/src/main/res/drawable-xhdpi/ic_drawer.png differ diff --git a/sample/src/main/res/drawable-xhdpi/ic_launcher.png b/sample/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000..71c6d76 Binary files /dev/null and b/sample/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/sample/src/main/res/drawable-xxhdpi/drawer_shadow.9.png b/sample/src/main/res/drawable-xxhdpi/drawer_shadow.9.png new file mode 100644 index 0000000..b91e9d7 Binary files /dev/null and b/sample/src/main/res/drawable-xxhdpi/drawer_shadow.9.png differ diff --git a/sample/src/main/res/drawable-xxhdpi/ic_drawer.png b/sample/src/main/res/drawable-xxhdpi/ic_drawer.png new file mode 100644 index 0000000..9c4685d Binary files /dev/null and b/sample/src/main/res/drawable-xxhdpi/ic_drawer.png differ diff --git a/sample/src/main/res/drawable-xxhdpi/ic_launcher.png b/sample/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..4df1894 Binary files /dev/null and b/sample/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/sample/src/main/res/layout/act_main.xml b/sample/src/main/res/layout/act_main.xml new file mode 100644 index 0000000..b8e9a4b --- /dev/null +++ b/sample/src/main/res/layout/act_main.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/values-w820dp/dimens.xml b/sample/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 0000000..63fc816 --- /dev/null +++ b/sample/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + 64dp + diff --git a/sample/src/main/res/values/dimens.xml b/sample/src/main/res/values/dimens.xml new file mode 100644 index 0000000..074e7a0 --- /dev/null +++ b/sample/src/main/res/values/dimens.xml @@ -0,0 +1,9 @@ + + + 16dp + 16dp + + + 240dp + diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml new file mode 100644 index 0000000..385a7d0 --- /dev/null +++ b/sample/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + + + PackerSample + + diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml new file mode 100644 index 0000000..766ab99 --- /dev/null +++ b/sample/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..c1d1ef1 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':plugin' diff --git a/test-build.sh b/test-build.sh new file mode 100755 index 0000000..0d6cf67 --- /dev/null +++ b/test-build.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +./deploy-local.sh +echo "test clean build" +cd sample +../gradlew clean build --stacktrace $1 $2 +cd .. diff --git a/test-market.sh b/test-market.sh new file mode 100755 index 0000000..5dfb707 --- /dev/null +++ b/test-market.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +./deploy-local.sh +echo "test market and archives apk build." +cd sample +../gradlew -Pmarket=markets.txt clean archiveApkRelease --stacktrace $1 $2 +cd ..