+ * author: blankj
+ * blog : http://blankj.com
+ * time : 2019/07/13
+ * desc :
+ *
+ */
+class GLog {
+
+ def static debugSwitch = true
+
+ static d(Object... contents) {
+ if (!debugSwitch) return contents
+ return l(contents)
+ }
+
+ static l(Object... contents) {
+ StringBuilder sb = new StringBuilder()
+ sb.append(LogConst.BORDER_TOP)
+ sb.append(borderMsg(processContents(contents)))
+ sb.append(LogConst.BORDER_BTM)
+ print sb.toString()
+ return contents
+ }
+
+ private static borderMsg(String msg) {
+ StringBuilder sb = new StringBuilder()
+ object2String(msg).split(LogConst.LINE_SEP).each { line ->
+ sb.append(LogConst.BORDER_LFT).append(line).append(LogConst.LINE_SEP)
+ }
+ return sb
+ }
+
+ private static processContents(final Object... contents) {
+ String body = LogConst.NULL
+ if (contents != null) {
+ if (contents.length == 1) {
+ body = object2String(contents[0])
+ } else {
+ StringBuilder sb = new StringBuilder()
+ int i = 0
+ for (int len = contents.length; i < len; ++i) {
+ Object content = contents[i]
+ sb.append("args[$i] = ")
+ .append(object2String(content))
+ .append(LogConst.LINE_SEP)
+ }
+ body = sb.toString()
+ }
+ }
+ return body.length() == 0 ? LogConst.NOTHING : body
+ }
+
+ static String object2String(Object object) {
+ if (object == null) return "null";
+ if (object.getClass().isArray()) return LogFormatter.array2String(object);
+ if (object instanceof List) return LogFormatter.list2String(object);
+ if (object instanceof Map) return LogFormatter.map2String(object);
+ if (object instanceof Throwable) return LogFormatter.throwable2String(object);
+ return object.toString();
+ }
+
+ static class LogFormatter {
+
+ private static array2String(Object object) {
+ if (object instanceof Object[]) {
+ return Arrays.deepToString((Object[]) object);
+ } else if (object instanceof boolean[]) {
+ return Arrays.toString((boolean[]) object);
+ } else if (object instanceof byte[]) {
+ return Arrays.toString((byte[]) object);
+ } else if (object instanceof char[]) {
+ return Arrays.toString((char[]) object);
+ } else if (object instanceof double[]) {
+ return Arrays.toString((double[]) object);
+ } else if (object instanceof float[]) {
+ return Arrays.toString((float[]) object);
+ } else if (object instanceof int[]) {
+ return Arrays.toString((int[]) object);
+ } else if (object instanceof long[]) {
+ return Arrays.toString((long[]) object);
+ } else if (object instanceof short[]) {
+ return Arrays.toString((short[]) object);
+ }
+ throw new IllegalArgumentException("Array has incompatible type: " + object.getClass());
+ }
+
+ private static list2String(List list) {
+ StringBuilder sb = new StringBuilder()
+ sb.append("[")
+ list.each { v ->
+ if (v instanceof Map || v instanceof List) {
+ sb.append(String.format("$LogConst.LINE_SEP%${deep++ * 8}s${object2String(v)},", ""))
+ deep--
+ } else {
+ sb.append(String.format("$LogConst.LINE_SEP%${deep * 8}s$v,", ""))
+ }
+ }
+ sb.deleteCharAt(sb.length() - 1)
+ if (deep - 1 == 0) {
+ sb.append("$LogConst.LINE_SEP]")
+ } else {
+ sb.append(String.format("$LogConst.LINE_SEP%${(deep - 1) * 8}s]", ""))
+ }
+ return sb.toString()
+ }
+
+ private static deep = 1;
+
+ private static map2String(Map map) {
+ StringBuilder sb = new StringBuilder()
+ sb.append("[")
+ map.each { k, v ->
+ if (v instanceof Map || v instanceof List) {
+ sb.append(String.format("$LogConst.LINE_SEP%${deep++ * 8}s%-26s: ${object2String(v)},", "", k))
+ deep--
+ } else {
+ sb.append(String.format("$LogConst.LINE_SEP%${deep * 8}s%-26s: $v,", "", k))
+ }
+ }
+ sb.deleteCharAt(sb.length() - 1)
+ if (deep - 1 == 0) {
+ sb.append("$LogConst.LINE_SEP]")
+ } else {
+ sb.append(String.format("$LogConst.LINE_SEP%${(deep - 1) * 8}s]", ""))
+ }
+ return sb.toString()
+ }
+
+ private static throwable2String(Throwable throwable) {
+ final List throwableList = new ArrayList<>();
+ while (throwable != null && !throwableList.contains(throwable)) {
+ throwableList.add(throwable);
+ throwable = throwable.getCause();
+ }
+ final int size = throwableList.size();
+ final List frames = new ArrayList<>();
+ List nextTrace = getStackFrameList(throwableList.get(size - 1));
+ for (int i = size; --i >= 0;) {
+ final List trace = nextTrace;
+ if (i != 0) {
+ nextTrace = getStackFrameList(throwableList.get(i - 1));
+ removeCommonFrames(trace, nextTrace);
+ }
+ if (i == size - 1) {
+ frames.add(throwableList.get(i).toString());
+ } else {
+ frames.add(" Caused by: " + throwableList.get(i).toString());
+ }
+ frames.addAll(trace);
+ }
+ StringBuilder sb = new StringBuilder();
+ for (final String element : frames) {
+ sb.append(element).append(LogConst.LINE_SEP);
+ }
+ return sb.toString();
+ }
+
+ private static List getStackFrameList(final Throwable throwable) {
+ final StringWriter sw = new StringWriter();
+ final PrintWriter pw = new PrintWriter(sw, true);
+ throwable.printStackTrace(pw);
+ final String stackTrace = sw.toString();
+ final StringTokenizer frames = new StringTokenizer(stackTrace, LogConst.LINE_SEP);
+ final List list = new ArrayList<>();
+ boolean traceStarted = false;
+ while (frames.hasMoreTokens()) {
+ final String token = frames.nextToken();
+ // Determine if the line starts with at
+ final int at = token.indexOf("at");
+ if (at != -1 && token.substring(0, at).trim().isEmpty()) {
+ traceStarted = true;
+ list.add(token);
+ } else if (traceStarted) {
+ break;
+ }
+ }
+ return list;
+ }
+
+ private static void removeCommonFrames(final List causeFrames, final List wrapperFrames) {
+ int causeFrameIndex = causeFrames.size() - 1;
+ int wrapperFrameIndex = wrapperFrames.size() - 1;
+ while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) {
+ // Remove the frame from the cause trace if it is the same
+ // as in the wrapper trace
+ final String causeFrame = causeFrames.get(causeFrameIndex);
+ final String wrapperFrame = wrapperFrames.get(wrapperFrameIndex);
+ if (causeFrame.equals(wrapperFrame)) {
+ causeFrames.remove(causeFrameIndex);
+ }
+ causeFrameIndex--;
+ wrapperFrameIndex--;
+ }
+ }
+ }
+
+ static class LogConst {
+ static LINE_SEP = System.getProperty("line.separator");
+ static BORDER_TOP = "┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────" + LINE_SEP
+ static BORDER_LFT = "│ ";
+ static BORDER_BTM = "└────────────────────────────────────────────────────────────────────────────────────────────────────────────────" + LINE_SEP
+
+ static final NOTHING = "log nothing";
+ static final NULL = "null";
+ }
+}
diff --git a/buildSrc/src/main/groovy/LibConfig.groovy b/buildSrc/src/main/groovy/LibConfig.groovy
new file mode 100644
index 0000000000..6369553ba0
--- /dev/null
+++ b/buildSrc/src/main/groovy/LibConfig.groovy
@@ -0,0 +1,29 @@
+class LibConfig {
+
+ String path
+
+ String getGroupId() {
+ String[] splits = path.split(":")
+ return splits.length == 3 ? splits[0] : null
+ }
+
+ String getArtifactId() {
+ String[] splits = path.split(":")
+ return splits.length == 3 ? splits[1] : null
+ }
+
+ String getVersion() {
+ String[] splits = path.split(":")
+ return splits.length == 3 ? splits[2] : null
+ }
+
+ @Override
+ String toString() {
+ return "LibConfig { path = $path }"
+ }
+
+ static String getFlag(boolean b) {
+ return b ? "✅" : "❌"
+ }
+}
+
diff --git a/buildSrc/src/main/groovy/ModuleConfig.groovy b/buildSrc/src/main/groovy/ModuleConfig.groovy
new file mode 100644
index 0000000000..291abd8ffe
--- /dev/null
+++ b/buildSrc/src/main/groovy/ModuleConfig.groovy
@@ -0,0 +1,35 @@
+class ModuleConfig {
+
+ boolean isApply // 是否应用
+ boolean useLocal // 是否使用本地的
+ String localPath // 本地路径
+ String remotePath // 远程路径
+ def dep // 根据条件生成项目最终的依赖项
+
+ String getGroupId() {
+ String[] splits = remotePath.split(":")
+ return splits.length == 3 ? splits[0] : null
+ }
+
+ String getArtifactId() {
+ String[] splits = remotePath.split(":")
+ return splits.length == 3 ? splits[1] : null
+ }
+
+ String getVersion() {
+ String[] splits = remotePath.split(":")
+ return splits.length == 3 ? splits[2] : null
+ }
+
+ @Override
+ String toString() {
+ return "ModuleConfig { isApply = ${getFlag(isApply)}" +
+ ", dep = " + dep +
+ " }"
+ }
+
+ static String getFlag(boolean b) {
+ return b ? "✅" : "❌"
+ }
+}
+
diff --git a/buildSrc/src/main/groovy/PluginConfig.groovy b/buildSrc/src/main/groovy/PluginConfig.groovy
new file mode 100644
index 0000000000..3811c6a0ca
--- /dev/null
+++ b/buildSrc/src/main/groovy/PluginConfig.groovy
@@ -0,0 +1,35 @@
+final class PluginConfig {
+
+ boolean isApply = true // 是否应用
+ boolean useLocal // 是否使用本地的
+ String path // 插件路径
+ String id // 插件 ID
+
+ String getGroupId() {
+ String[] splits = path.split(":")
+ return splits.length == 3 ? splits[0] : null
+ }
+
+ String getArtifactId() {
+ String[] splits = path.split(":")
+ return splits.length == 3 ? splits[1] : null
+ }
+
+ String getVersion() {
+ String[] splits = path.split(":")
+ return splits.length == 3 ? splits[2] : null
+ }
+
+ @Override
+ String toString() {
+ return "PluginConfig { isApply = ${getFlag(isApply)}" +
+ ", useLocal = ${getFlag(useLocal)}" +
+ ", path = " + path +
+ ", id = " + id +
+ " }"
+ }
+
+ static String getFlag(boolean b) {
+ return b ? "✅" : "❌"
+ }
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/TaskDurationUtils.groovy b/buildSrc/src/main/groovy/TaskDurationUtils.groovy
new file mode 100644
index 0000000000..6aacfcf30f
--- /dev/null
+++ b/buildSrc/src/main/groovy/TaskDurationUtils.groovy
@@ -0,0 +1,91 @@
+import org.gradle.BuildListener
+import org.gradle.BuildResult
+import org.gradle.api.Task
+import org.gradle.api.execution.TaskExecutionListener
+import org.gradle.api.initialization.Settings
+import org.gradle.api.invocation.Gradle
+import org.gradle.api.tasks.TaskState
+
+import java.text.SimpleDateFormat
+
+/**
+ *
+ * author: blankj
+ * blog : http://blankj.com
+ * time : 2019/11/22
+ * desc :
+ *
+ */
+class TaskDurationUtils {
+
+ static List taskInfoList = []
+ static long startMillis
+
+ static init(Gradle grd) {
+ startMillis = System.currentTimeMillis()
+ grd.addListener(new TaskExecutionListener() {
+ @Override
+ void beforeExecute(Task task) {
+ task.ext.startTime = System.currentTimeMillis()
+ }
+
+ @Override
+ void afterExecute(Task task, TaskState state) {
+ def exeDuration = System.currentTimeMillis() - task.ext.startTime
+ if (exeDuration >= 500) {
+ taskInfoList.add(new TaskInfo(task: task, exeDuration: exeDuration))
+ }
+ }
+ })
+ grd.addBuildListener(new BuildListener() {
+ @Override
+ void beforeSettings(Settings settings) {
+ super.beforeSettings(settings)
+ }
+
+ @Override
+ void buildStarted(Gradle gradle) {}
+
+ @Override
+ void settingsEvaluated(Settings settings) {}
+
+ @Override
+ void projectsLoaded(Gradle gradle) {}
+
+ @Override
+ void projectsEvaluated(Gradle gradle) {}
+
+ @Override
+ void buildFinished(BuildResult buildResult) {
+ if (!taskInfoList.isEmpty()) {
+ Collections.sort(taskInfoList, new Comparator() {
+ @Override
+ int compare(TaskInfo t, TaskInfo t1) {
+ return t1.exeDuration - t.exeDuration
+ }
+ })
+ StringBuilder sb = new StringBuilder()
+ int buildSec = (System.currentTimeMillis() - startMillis) / 1000;
+ int m = buildSec / 60;
+ int s = buildSec % 60;
+ def timeInfo = (m == 0 ? "${s}s" : "${m}m ${s}s (${buildSec}s)")
+ sb.append("BUILD FINISHED in $timeInfo\n")
+ taskInfoList.each {
+ sb.append(String.format("%7sms %s\n", it.exeDuration, it.task.path))
+ }
+ def content = sb.toString()
+ GLog.d(content)
+ File file = new File(grd.rootProject.buildDir.getAbsolutePath(),
+ "build_time_records_" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()) + ".txt")
+ file.getParentFile().mkdirs()
+ file.write(content)
+ }
+ }
+ })
+ }
+
+ private static class TaskInfo {
+ Task task
+ long exeDuration
+ }
+}
diff --git a/buildSrc/src/main/java/com/blankj/plugin/readme/FormatUtils.groovy b/buildSrc/src/main/java/com/blankj/plugin/readme/FormatUtils.groovy
new file mode 100644
index 0000000000..f617ff2eae
--- /dev/null
+++ b/buildSrc/src/main/java/com/blankj/plugin/readme/FormatUtils.groovy
@@ -0,0 +1,42 @@
+package com.blankj.plugin.readme
+
+class FormatUtils {
+
+ static def LINE_SEP = System.getProperty("line.separator")
+ static def LONG_SPACE = " "
+
+ static def format(File readmeCN) {
+ def sb = new StringBuilder(),
+ lines = readmeCN.readLines("UTF-8"),
+ i = 0,
+ size = lines.size()
+ for (; i < size; ++i) {
+ String line = lines.get(i)
+ if (line.contains("* ###")) {
+ sb.append(line).append(LINE_SEP)
+ .append("```").append(LINE_SEP)
+ def maxLen = 0
+ line = lines.get(i += 2)
+ // get the max length of space
+ for (def j = i; !line.equals("```"); line = lines.get(++j)) {
+ maxLen = Math.max(maxLen, line.replace(" ", "").replace(",", ", ").indexOf(':'))
+ }
+ line = lines.get(i)
+ for (; !line.equals("```"); line = lines.get(++i)) {
+ def noSpaceLine = line.replace(" ", "")
+ def spaceLen = maxLen - line.replace(" ", "").replace(",", ", ").indexOf(':')
+ sb.append(noSpaceLine.substring(0, noSpaceLine.indexOf(':')).replace(",", ", "))
+ .append(LONG_SPACE.substring(0, spaceLen))// add the space
+ .append(': ')
+ .append(line.substring(line.indexOf(':') + 1).trim())
+ .append(LINE_SEP)
+ }
+ sb.append("```")
+ } else {
+ sb.append(line)
+ }
+ sb.append(LINE_SEP)
+ }
+ readmeCN.write(sb.toString(), "UTF-8")
+ }
+}
diff --git a/buildSrc/src/main/java/com/blankj/plugin/readme/ReadmeCorePlugin.groovy b/buildSrc/src/main/java/com/blankj/plugin/readme/ReadmeCorePlugin.groovy
new file mode 100644
index 0000000000..8f3cf47780
--- /dev/null
+++ b/buildSrc/src/main/java/com/blankj/plugin/readme/ReadmeCorePlugin.groovy
@@ -0,0 +1,52 @@
+package com.blankj.plugin.readme
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+class ReadmeCorePlugin implements Plugin {
+
+ @Override
+ void apply(Project project) {
+ project.extensions.create('readme', ReadmeExtension)
+
+ project.task('readmeTask') {
+ doLast {
+ println "readmeTask start..."
+
+ def ext = project['readme'] as ReadmeExtension
+ def readmeCN = ext.readmeCnFile
+ def readmeEng = ext.readmeFile
+
+ readmeOfUtilCode2Eng(readmeCN, readmeEng)
+
+ println "readmeTask finished."
+ }
+ }
+ }
+
+ static def readmeOfUtilCode2Eng(File readmeCN, File readmeEng) {
+ FormatUtils.format(readmeCN)
+ def lines = readmeCN.readLines("UTF-8")
+ def sb = new StringBuilder()
+ readmeCN.eachLine { line ->
+ if (line.contains("* ###")) {
+ if (line.contains("UtilsTransActivity")) {
+ sb.append(line)
+ } else {
+ String utilsName = line.substring(line.indexOf("[") + 1, line.indexOf("Utils"))
+ sb.append("* ### About ").append(utilsName).append(line.substring(line.indexOf(" -> ")))
+ }
+ } else if (line.contains(": ") && !line.contains("[")) {
+ sb.append(line.substring(0, line.indexOf(':')).trim())
+ } else if (line.contains("打个小广告") || line.contains("基你太美")) {
+ return
+ } else {
+ sb.append(line)
+ }
+ sb.append(FormatUtils.LINE_SEP)
+ }
+ readmeEng.write(sb.toString(), "UTF-8")
+ }
+}
+
+
diff --git a/buildSrc/src/main/java/com/blankj/plugin/readme/ReadmeExtension.groovy b/buildSrc/src/main/java/com/blankj/plugin/readme/ReadmeExtension.groovy
new file mode 100644
index 0000000000..789e683c80
--- /dev/null
+++ b/buildSrc/src/main/java/com/blankj/plugin/readme/ReadmeExtension.groovy
@@ -0,0 +1,8 @@
+package com.blankj.plugin.readme
+
+class ReadmeExtension {
+
+ File readmeFile
+ File readmeCnFile
+
+}
diff --git a/buildSrc/src/main/java/com/blankj/plugin/readme/ReadmeSubPlugin.groovy b/buildSrc/src/main/java/com/blankj/plugin/readme/ReadmeSubPlugin.groovy
new file mode 100644
index 0000000000..bade51bbf4
--- /dev/null
+++ b/buildSrc/src/main/java/com/blankj/plugin/readme/ReadmeSubPlugin.groovy
@@ -0,0 +1,49 @@
+package com.blankj.plugin.readme
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+class ReadmeSubPlugin implements Plugin {
+
+ @Override
+ void apply(Project project) {
+ project.extensions.create('readme', ReadmeExtension)
+
+ project.task('readmeTask') {
+ doLast {
+ println "readmeTask start..."
+
+ def ext = project['readme'] as ReadmeExtension
+ def readmeCN = ext.readmeCnFile
+ def readmeEng = ext.readmeFile
+
+ readmeOfSubUtil2Eng(readmeCN, readmeEng)
+
+ println "readmeTask finished."
+ }
+ }
+ }
+
+ static def readmeOfSubUtil2Eng(File readmeCN, File readmeEng) {
+ FormatUtils.format(readmeCN)
+ def lines = readmeCN.readLines("UTF-8"),
+ sb = new StringBuilder("## How to use" + FormatUtils.LINE_SEP
+ + FormatUtils.LINE_SEP +
+ "You should copy the following classes which you want to use in your project." + FormatUtils.LINE_SEP),
+ i = 3,
+ size = lines.size()
+ for (; i < size; ++i) {
+ String line = lines.get(i)
+ if (line.contains("* ###")) {
+ String utilsName = line.substring(line.indexOf("[") + 1, line.indexOf("Utils"))
+ sb.append("* ### About ").append(utilsName).append(line.substring(line.indexOf(" -> ")))
+ } else if (line.contains(": ") && !line.contains("[")) {
+ sb.append(line.substring(0, line.indexOf(':')).trim())
+ } else {
+ sb.append(line)
+ }
+ sb.append(FormatUtils.LINE_SEP)
+ }
+ readmeEng.write(sb.toString(), "UTF-8")
+ }
+}
diff --git a/config/flavor.gradle b/config/flavor.gradle
new file mode 100644
index 0000000000..25c1801983
--- /dev/null
+++ b/config/flavor.gradle
@@ -0,0 +1,22 @@
+android {
+ flavorDimensions "env"
+ productFlavors {
+ dev {
+ dimension "env"
+ }
+
+ production {
+ dimension "env"
+ }
+ }
+
+ variantFilter { variant ->
+ def flavorNames = variant.flavors*.name
+ def buildTypeName = variant.buildType.name
+
+ // production 包不允许 debug 构建
+ if (flavorNames.contains("production") && buildTypeName.contains("debug")) {
+ variant.setIgnore(true)
+ }
+ }
+}
\ No newline at end of file
diff --git a/config/publish.gradle b/config/publish.gradle
new file mode 100644
index 0000000000..7608215123
--- /dev/null
+++ b/config/publish.gradle
@@ -0,0 +1,226 @@
+/*
+ 1. add
+ signing.keyId=xx
+ signing.password=xx
+ signing.secretKeyRingFile=/Users/xx/secring.gpg
+ ossrhUsername=xx
+ ossrhPassword=xx
+ in root local.properties
+
+ 2. copy the file to the directory of gradle, and apply the file in the module
+ ext {
+ groupId = Config.modules.lib_utilcode.groupId
+ artifactId = Config.modules.lib_utilcode.artifactId
+ version = Config.modules.lib_utilcode.version
+ website = "https://github.com/Blankj/AndroidUtilCode"
+}
+ apply from: "${rootDir.path}/config/publish.gradle"
+
+ 3. execute following command to publish
+ ./gradlew :xxmodule:publish2Local -> upload to mavenLocal
+ ./gradlew :xxmodule:publish2Remote -> upload to mavenCentral
+*/
+
+apply plugin: 'maven-publish'
+apply plugin: 'signing'
+
+ext.multiPublishMode = true
+
+File localPropertiesFile = project.rootProject.file("local.properties");
+if (!localPropertiesFile.exists()) {
+ return
+}
+
+Properties properties = new Properties()
+properties.load(new FileInputStream(localPropertiesFile))
+properties.each { name, value -> ext[name] = value }
+
+afterEvaluate {
+ def ext = project.ext
+ publishing {
+ publications {
+ release(MavenPublication) {
+ groupId ext.groupId
+ artifactId ext.artifactId
+ version ext.version
+
+ if (isAndroidEnv(project)) {
+ if (project.ext.multiPublishMode) {
+ artifact("$buildDir/outputs/aar/${project.getName()}-release.aar")
+ artifact sourcesJar
+ } else {
+ from project.components.release
+ }
+ } else {
+ from project.components.java
+ }
+
+ pom {
+ name = ext.artifactId
+ description = ext.artifactId
+ url = ext.website
+
+ licenses {
+ license {
+ name = 'The Apache Software License, Version 2.0'
+ url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+ }
+ }
+ developers {
+ developer {
+ id = ext.ossrhUsername
+ name = ext.ossrhUsername
+ }
+ }
+ scm {
+ url = ext.website
+ connection = ext.website
+ developerConnection = ext.website + ".git"
+ }
+
+ if (project.ext.multiPublishMode) {
+ withXml {
+ def dependenciesNode = asNode().getAt('dependencies')[0] ?:
+ asNode().appendNode('dependencies')
+
+ configurations.api.getDependencies().each {
+ dep -> addDependency(project, dependenciesNode, dep, "compile")
+ }
+ configurations.implementation.getDependencies().each {
+ dep -> addDependency(project, dependenciesNode, dep, "runtime")
+ }
+ }
+ }
+ }
+ }
+ }
+
+ repositories {
+ maven {
+ // s01 is newest
+ def releasesUrl = "https://s01.oss.sonatype.org/content/repositories/releases/"
+ def snapshotUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
+ url = version.toUpperCase().endsWith('SNAPSHOT') ? snapshotUrl : releasesUrl
+
+ credentials {
+ username ossrhUsername
+ password ossrhPassword
+ }
+ }
+ }
+ }
+
+ signing {
+ sign publishing.publications
+ }
+}
+
+private void addDependency(Project project, def dependenciesNode, Dependency dep, String scope) {
+ if (dep.group == null || dep.version == null || dep.name == null || dep.name == "unspecified") {
+ return
+ }
+
+ final dependencyNode = dependenciesNode.appendNode('dependency')
+ dependencyNode.appendNode('scope', scope)
+
+ if (dep.version == 'unspecified') {
+ // 检测 module 中的 dependencies 是否有源码依赖
+ // 如果是源码依赖,而且没有在 config 中配置 remotePath,
+ // 那么发布到仓库,其他地方依赖该库时会找不到源码的那个库
+ println "publish -> module(unspecified) <${dep.group}:${dep.name}:${dep.version}>"
+ if (project.ext.groupId || project.ext.version) {
+ throw new GradleException("The module of <" + dep.name + "> should set groupId & version.")
+ }
+ // 源码依赖,但配置了 remotePath,让 pom 中写入 remotePath
+ println("publish -> module(wrapped) <${project.ext.groupId}:${name}:${project.ext.version}>")
+
+ dependencyNode.appendNode('groupId', project.ext.pomGroupID)
+ dependencyNode.appendNode('artifactId', dep.name)
+ dependencyNode.appendNode('version', project.ext.pomVersion)
+ } else {
+ dependencyNode.appendNode('groupId', dep.group)
+ dependencyNode.appendNode('artifactId', dep.name)
+ dependencyNode.appendNode('version', dep.version)
+ println("publish -> library <${dep.group}:${dep.name}:${dep.version}>")
+ }
+
+ if (!dep.transitive) {
+ // In case of non transitive dependency,
+ // all its dependencies should be force excluded from them POM file
+ final exclusionNode = dependencyNode.appendNode('exclusions').appendNode('exclusion')
+ exclusionNode.appendNode('groupId', '*')
+ exclusionNode.appendNode('artifactId', '*')
+ } else if (!dep.properties.excludeRules.empty) {
+ // For transitive with exclusions, all exclude rules should be added to the POM file
+ final exclusions = dependencyNode.appendNode('exclusions')
+ dep.properties.excludeRules.each { ExcludeRule rule ->
+ final exclusionNode = exclusions.appendNode('exclusion')
+ exclusionNode.appendNode('groupId', rule.group ?: '*')
+ exclusionNode.appendNode('artifactId', rule.module ?: '*')
+ }
+ }
+}
+
+if (isAndroidEnv(project)) {
+ // This generates sources.jar
+ task sourcesJar(type: Jar) {
+ classifier = 'sources'
+ from android.sourceSets.main.java.source
+ }
+
+ task javadoc(type: Javadoc) {
+ source = android.sourceSets.main.java.source
+ classpath += configurations.compile
+ classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
+ }
+
+ task javadocJar(type: Jar, dependsOn: javadoc) {
+ classifier = 'javadoc'
+ from javadoc.destinationDir
+ }
+} else {
+ task sourcesJar(type: Jar, dependsOn: classes) {
+ classifier = 'sources'
+ from sourceSets.main.allSource
+ }
+
+ task javadocJar(type: Jar, dependsOn: javadoc) {
+ classifier = 'javadoc'
+ from javadoc.destinationDir
+ }
+}
+
+if (project.hasProperty("kotlin")) {
+ // Disable creating javadocs
+ project.tasks.withType(Javadoc) {
+ enabled = false
+ }
+}
+
+javadoc {
+ options {
+ encoding "UTF-8"
+ charSet 'UTF-8'
+ author true
+ version project.ext.version
+ links "http://docs.oracle.com/javase/7/docs/api"
+ title "${project.ext.artifactId} ${project.ext.version}"
+ }
+}
+
+artifacts {
+ archives javadocJar
+ archives sourcesJar
+}
+
+static def isAndroidEnv(Project project) {
+ return project.getPlugins().hasPlugin('com.android.application') || project.getPlugins().hasPlugin('com.android.library')
+}
+
+task publish2Local(type: GradleBuild) {
+ tasks = ['assemble', 'publishReleasePublicationToMavenLocal']
+}
+
+task publish2Remote(type: GradleBuild) {
+ tasks = ['assemble', 'publishReleasePublicationToMavenRepository']
+}
\ No newline at end of file
diff --git a/feature/launcher/app/.gitignore b/feature/launcher/app/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/feature/launcher/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/feature/launcher/app/build.gradle b/feature/launcher/app/build.gradle
new file mode 100644
index 0000000000..8f2b6d205d
--- /dev/null
+++ b/feature/launcher/app/build.gradle
@@ -0,0 +1,5 @@
+apply plugin: 'kotlin-kapt'
+
+dependencies {
+ kapt Config.libs.eventbus_processor.path
+}
\ No newline at end of file
diff --git a/feature/launcher/app/proguard-rules.pro b/feature/launcher/app/proguard-rules.pro
new file mode 100644
index 0000000000..f1b424510d
--- /dev/null
+++ b/feature/launcher/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/feature/launcher/app/src/main/AndroidManifest.xml b/feature/launcher/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..4f033c06a6
--- /dev/null
+++ b/feature/launcher/app/src/main/AndroidManifest.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feature/launcher/app/src/main/java/com/blankj/launcher/app/LauncherApp.java b/feature/launcher/app/src/main/java/com/blankj/launcher/app/LauncherApp.java
new file mode 100644
index 0000000000..7dc22cee69
--- /dev/null
+++ b/feature/launcher/app/src/main/java/com/blankj/launcher/app/LauncherApp.java
@@ -0,0 +1,28 @@
+package com.blankj.launcher.app;
+
+import com.blankj.common.CommonApplication;
+
+/**
+ *
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 2016/10/12
+ * desc :
+ *
+ */
+public class LauncherApp extends CommonApplication {
+
+ private static LauncherApp sInstance;
+
+ public static LauncherApp getInstance() {
+ return sInstance;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ sInstance = this;
+ }
+}
+
+
diff --git a/feature/main/app/.gitignore b/feature/main/app/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/feature/main/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/feature/main/app/build.gradle b/feature/main/app/build.gradle
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/feature/main/app/proguard-rules.pro b/feature/main/app/proguard-rules.pro
new file mode 100644
index 0000000000..f1b424510d
--- /dev/null
+++ b/feature/main/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/feature/main/app/src/main/AndroidManifest.xml b/feature/main/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..bb8c3398d4
--- /dev/null
+++ b/feature/main/app/src/main/AndroidManifest.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feature/main/app/src/main/java/com/blankj/main/app/MainApp.java b/feature/main/app/src/main/java/com/blankj/main/app/MainApp.java
new file mode 100644
index 0000000000..7b083b92ba
--- /dev/null
+++ b/feature/main/app/src/main/java/com/blankj/main/app/MainApp.java
@@ -0,0 +1,35 @@
+package com.blankj.main.app;
+
+import android.content.Context;
+
+import com.blankj.common.CommonApplication;
+
+/**
+ *
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 2016/10/12
+ * desc :
+ *
+ */
+public class MainApp extends CommonApplication {
+
+ private static MainApp sInstance;
+
+ public static MainApp getInstance() {
+ return sInstance;
+ }
+
+ @Override
+ protected void attachBaseContext(Context base) {
+ super.attachBaseContext(base);
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ sInstance = this;
+ }
+}
+
+
diff --git a/feature/main/pkg/.gitignore b/feature/main/pkg/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/feature/main/pkg/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/feature/main/pkg/build.gradle b/feature/main/pkg/build.gradle
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/feature/main/pkg/proguard-rules.pro b/feature/main/pkg/proguard-rules.pro
new file mode 100644
index 0000000000..f1b424510d
--- /dev/null
+++ b/feature/main/pkg/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/feature/main/pkg/src/main/AndroidManifest.xml b/feature/main/pkg/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..1cfdba8f69
--- /dev/null
+++ b/feature/main/pkg/src/main/AndroidManifest.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/feature/main/pkg/src/main/java/com/blankj/main/pkg/MainActivity.kt b/feature/main/pkg/src/main/java/com/blankj/main/pkg/MainActivity.kt
new file mode 100644
index 0000000000..b00aba9585
--- /dev/null
+++ b/feature/main/pkg/src/main/java/com/blankj/main/pkg/MainActivity.kt
@@ -0,0 +1,74 @@
+package com.blankj.main.pkg
+
+import android.graphics.Color
+import android.os.Bundle
+import android.view.View
+import androidx.appcompat.app.ActionBarDrawerToggle
+import com.blankj.common.activity.CommonActivity
+import com.blankj.common.item.CommonItem
+import com.blankj.common.item.CommonItemClick
+import com.blankj.subutil.export.api.SubUtilApi
+import com.blankj.utilcode.export.api.UtilCodeApi
+import com.blankj.utilcode.util.ApiUtils
+import com.blankj.utilcode.util.BarUtils
+import com.blankj.utilcode.util.ClickUtils
+import com.blankj.utilcode.util.CollectionUtils
+import kotlinx.android.synthetic.main.activity_main.*
+
+
+/**
+ * ```
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 2016/09/29
+ * desc : MainActivity
+ * ```
+ */
+class MainActivity : CommonActivity() {
+
+ override fun isSwipeBack(): Boolean {
+ return false
+ }
+
+ override fun bindDrawer(): Boolean {
+ return true
+ }
+
+ override fun bindLayout(): Int {
+ return R.layout.activity_main
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ window.setBackgroundDrawable(null)
+ super.onCreate(savedInstanceState)
+ }
+
+ override fun initView(savedInstanceState: Bundle?, contentView: View?) {
+ super.initView(savedInstanceState, contentView)
+ setCommonItems(mainRv, CollectionUtils.newArrayList>(
+ CommonItemClick(R.string.core_util, true) {
+ ApiUtils.getApi(UtilCodeApi::class.java)?.startUtilCodeActivity(this)
+ },
+ CommonItemClick(R.string.sub_util, true) {
+ ApiUtils.getApi(SubUtilApi::class.java)?.startSubUtilActivity(this)
+ }
+ ))
+
+ launcherMainCtl.setExpandedTitleColor(Color.TRANSPARENT)
+ setSupportActionBar(launcherMainToolbar)
+ val toggle = ActionBarDrawerToggle(this,
+ drawerView.mBaseDrawerRootLayout,
+ launcherMainToolbar,
+ R.string.navigation_drawer_open,
+ R.string.navigation_drawer_close)
+ drawerView.mBaseDrawerRootLayout.addDrawerListener(toggle)
+ toggle.syncState()
+
+ BarUtils.setStatusBarColor4Drawer(drawerView.mBaseDrawerRootLayout, launcherMainFakeStatusBar, Color.TRANSPARENT, false)
+ BarUtils.addMarginTopEqualStatusBarHeight(launcherMainToolbar)
+ }
+
+ override fun onBackPressed() {
+ ClickUtils.back2HomeFriendly("Press again to exit.")
+ }
+}
diff --git a/feature/main/pkg/src/main/java/com/blankj/main/pkg/SplashActivity.kt b/feature/main/pkg/src/main/java/com/blankj/main/pkg/SplashActivity.kt
new file mode 100644
index 0000000000..9f842cda06
--- /dev/null
+++ b/feature/main/pkg/src/main/java/com/blankj/main/pkg/SplashActivity.kt
@@ -0,0 +1,7 @@
+package com.blankj.main.pkg
+
+import com.blankj.common.activity.CommonActivity
+
+class SplashActivity : CommonActivity() {
+
+}
\ No newline at end of file
diff --git a/feature/main/pkg/src/main/res/layout/activity_main.xml b/feature/main/pkg/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000000..3a33dd0f8e
--- /dev/null
+++ b/feature/main/pkg/src/main/res/layout/activity_main.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/feature/main/pkg/src/main/res/values/strings.xml b/feature/main/pkg/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..7abc06d3ba
--- /dev/null
+++ b/feature/main/pkg/src/main/res/values/strings.xml
@@ -0,0 +1 @@
+
diff --git a/feature/mock/.gitignore b/feature/mock/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/feature/mock/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/feature/mock/build.gradle b/feature/mock/build.gradle
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/feature/mock/proguard-rules.pro b/feature/mock/proguard-rules.pro
new file mode 100644
index 0000000000..f1b424510d
--- /dev/null
+++ b/feature/mock/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/feature/mock/src/main/AndroidManifest.xml b/feature/mock/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..8c0f9aa047
--- /dev/null
+++ b/feature/mock/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/feature/mock/src/main/java/com/blankj/mock/subutil/SubUtilApiMock.java b/feature/mock/src/main/java/com/blankj/mock/subutil/SubUtilApiMock.java
new file mode 100644
index 0000000000..6c5af69924
--- /dev/null
+++ b/feature/mock/src/main/java/com/blankj/mock/subutil/SubUtilApiMock.java
@@ -0,0 +1,25 @@
+package com.blankj.mock.subutil;
+
+import android.content.Context;
+
+import com.blankj.subutil.export.api.SubUtilApi;
+import com.blankj.utilcode.util.ApiUtils;
+import com.blankj.utilcode.util.ToastUtils;
+
+/**
+ *
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 2019/07/10
+ * desc :
+ *
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 2019/07/10
+ * desc :
+ *
+ */
+@ApiUtils.Api(isMock = true)
+public class UtilCodeApiMock extends UtilCodeApi {
+
+ @Override
+ public void startUtilCodeActivity(Context context) {
+ ToastUtils.showShort("startUtilCodeActivity");
+ }
+
+ @Override
+ public void testCallback(Callback callback) {
+ if (callback != null) {
+ callback.call();
+ }
+ }
+
+}
diff --git a/feature/subutil/app/.gitignore b/feature/subutil/app/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/feature/subutil/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/feature/subutil/app/build.gradle b/feature/subutil/app/build.gradle
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/feature/subutil/app/proguard-rules.pro b/feature/subutil/app/proguard-rules.pro
new file mode 100644
index 0000000000..f1b424510d
--- /dev/null
+++ b/feature/subutil/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/feature/subutil/app/src/main/AndroidManifest.xml b/feature/subutil/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..2989dbfebf
--- /dev/null
+++ b/feature/subutil/app/src/main/AndroidManifest.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feature/subutil/app/src/main/java/com/blankj/subutil/app/SubUtilApp.kt b/feature/subutil/app/src/main/java/com/blankj/subutil/app/SubUtilApp.kt
new file mode 100644
index 0000000000..9667490cf4
--- /dev/null
+++ b/feature/subutil/app/src/main/java/com/blankj/subutil/app/SubUtilApp.kt
@@ -0,0 +1,32 @@
+package com.blankj.subutil.app
+
+import android.content.Context
+import com.blankj.common.CommonApplication
+
+
+/**
+ * ```
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 2016/10/12
+ * desc : app about utils
+ * ```
+ */
+class SubUtilApp : CommonApplication() {
+
+ companion object {
+ var instance: SubUtilApp? = null
+ private set
+ }
+
+ override fun attachBaseContext(base: Context) {
+ super.attachBaseContext(base)
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ instance = this
+ }
+}
+
+
diff --git a/feature/subutil/export/.gitignore b/feature/subutil/export/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/feature/subutil/export/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/feature/subutil/export/build.gradle b/feature/subutil/export/build.gradle
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/feature/subutil/export/proguard-rules.pro b/feature/subutil/export/proguard-rules.pro
new file mode 100644
index 0000000000..f1b424510d
--- /dev/null
+++ b/feature/subutil/export/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/feature/subutil/export/src/main/AndroidManifest.xml b/feature/subutil/export/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..de00a6d73e
--- /dev/null
+++ b/feature/subutil/export/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/feature/subutil/export/src/main/java/com/blankj/subutil/export/api/SubUtilApi.java b/feature/subutil/export/src/main/java/com/blankj/subutil/export/api/SubUtilApi.java
new file mode 100644
index 0000000000..c0e4abe037
--- /dev/null
+++ b/feature/subutil/export/src/main/java/com/blankj/subutil/export/api/SubUtilApi.java
@@ -0,0 +1,20 @@
+package com.blankj.subutil.export.api;
+
+import android.content.Context;
+
+import com.blankj.utilcode.util.ApiUtils;
+
+
+/**
+ *
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 2019/06/09
+ * desc :
+ *
+ */
+public abstract class SubUtilApi extends ApiUtils.BaseApi {
+
+ public abstract void startSubUtilActivity(Context context);
+
+}
diff --git a/feature/subutil/pkg/.gitignore b/feature/subutil/pkg/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/feature/subutil/pkg/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/feature/subutil/pkg/build.gradle b/feature/subutil/pkg/build.gradle
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/feature/subutil/pkg/proguard-rules.pro b/feature/subutil/pkg/proguard-rules.pro
new file mode 100644
index 0000000000..f1b424510d
--- /dev/null
+++ b/feature/subutil/pkg/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/feature/subutil/pkg/src/main/AndroidManifest.xml b/feature/subutil/pkg/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..198eb5c31b
--- /dev/null
+++ b/feature/subutil/pkg/src/main/AndroidManifest.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/Config.kt b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/Config.kt
new file mode 100644
index 0000000000..82a8db84d3
--- /dev/null
+++ b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/Config.kt
@@ -0,0 +1,31 @@
+package com.blankj.subutil.pkg
+
+import android.os.Environment
+import com.blankj.utilcode.util.Utils
+
+/**
+ * ```
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 2017/05/10
+ * desc : config about constants
+ * ```
+ */
+object Config {
+
+ val FILE_SEP = System.getProperty("file.separator")
+ val LINE_SEP = System.getProperty("line.separator")
+ const val TEST_PKG = "com.blankj.testinstall"
+ private val CACHE_PATH: String
+ val TEST_APK_PATH: String
+
+ init {
+ val cacheDir = Utils.getApp().externalCacheDir
+ CACHE_PATH = if (cacheDir != null) {
+ cacheDir.absolutePath
+ } else {
+ Environment.getExternalStorageDirectory().absolutePath
+ } + FILE_SEP
+ TEST_APK_PATH = CACHE_PATH + "test_install.apk"
+ }
+}
diff --git a/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/SubUtilApiImpl.java b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/SubUtilApiImpl.java
new file mode 100644
index 0000000000..0b978f1bcd
--- /dev/null
+++ b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/SubUtilApiImpl.java
@@ -0,0 +1,25 @@
+package com.blankj.subutil.pkg;
+
+import android.content.Context;
+
+import com.blankj.subutil.export.api.SubUtilApi;
+import com.blankj.subutil.pkg.feature.SubUtilActivity;
+import com.blankj.utilcode.util.ApiUtils;
+
+/**
+ *
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 2019/07/02
+ * desc :
+ *
+ */
+@ApiUtils.Api
+public class SubUtilApiImpl extends SubUtilApi {
+
+ @Override
+ public void startSubUtilActivity(Context context) {
+ SubUtilActivity.Companion.start(context);
+ }
+
+}
diff --git a/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/SubUtilActivity.kt b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/SubUtilActivity.kt
new file mode 100644
index 0000000000..2c6af650ef
--- /dev/null
+++ b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/SubUtilActivity.kt
@@ -0,0 +1,61 @@
+package com.blankj.subutil.pkg.feature
+
+import android.content.Context
+import android.content.Intent
+import com.blankj.common.activity.CommonActivity
+import com.blankj.common.item.CommonItem
+import com.blankj.common.item.CommonItemClick
+import com.blankj.subutil.pkg.R
+import com.blankj.subutil.pkg.feature.appStore.AppStoreActivity
+import com.blankj.subutil.pkg.feature.battery.BatteryActivity
+import com.blankj.subutil.pkg.feature.country.CountryActivity
+import com.blankj.subutil.pkg.feature.dangerous.DangerousActivity
+import com.blankj.subutil.pkg.feature.location.LocationActivity
+import com.blankj.subutil.pkg.feature.pinyin.PinyinActivity
+import com.blankj.utilcode.util.CollectionUtils
+
+/**
+ * ```
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 2016/09/29
+ * desc : MainActivity
+ * ```
+ */
+class SubUtilActivity : CommonActivity() {
+
+ companion object {
+ fun start(context: Context) {
+ val starter = Intent(context, SubUtilActivity::class.java)
+ context.startActivity(starter)
+ }
+ }
+
+
+ override fun bindTitleRes(): Int {
+ return R.string.sub_util
+ }
+
+ override fun bindItems(): List> {
+ return CollectionUtils.newArrayList(
+ CommonItemClick(R.string.demo_app_store, true) {
+ AppStoreActivity.start(this)
+ },
+ CommonItemClick(R.string.demo_battery, true) {
+ BatteryActivity.start(this)
+ },
+ CommonItemClick(R.string.demo_country, true) {
+ CountryActivity.start(this)
+ },
+ CommonItemClick(R.string.demo_dangerous, true) {
+ DangerousActivity.start(this)
+ },
+ CommonItemClick(R.string.demo_location, true) {
+ LocationActivity.start(this)
+ },
+ CommonItemClick(R.string.demo_pinyin, true) {
+ PinyinActivity.start(this)
+ }
+ )
+ }
+}
diff --git a/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/appStore/AppStoreActivity.kt b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/appStore/AppStoreActivity.kt
new file mode 100644
index 0000000000..e7c2953448
--- /dev/null
+++ b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/appStore/AppStoreActivity.kt
@@ -0,0 +1,43 @@
+package com.blankj.subutil.pkg.feature.appStore
+
+import android.content.Context
+import android.content.Intent
+import com.blankj.common.activity.CommonActivity
+import com.blankj.common.item.CommonItem
+import com.blankj.common.item.CommonItemClick
+import com.blankj.subutil.pkg.R
+import com.blankj.subutil.util.AppStoreUtils
+import com.blankj.utilcode.util.ActivityUtils
+import com.blankj.utilcode.util.CollectionUtils
+
+/**
+ * ```
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 17/02/01
+ * desc : demo about AppStore
+ * ```
+ */
+class AppStoreActivity : CommonActivity() {
+
+ companion object {
+ fun start(context: Context) {
+ val starter = Intent(context, AppStoreActivity::class.java)
+ context.startActivity(starter)
+ }
+ }
+
+ override fun bindTitleRes(): Int {
+ return R.string.demo_app_store
+ }
+
+ override fun bindItems(): MutableList> {
+ return CollectionUtils.newArrayList(
+ CommonItemClick(R.string.app_store_system, true) {
+ AppStoreUtils.getAppStoreIntent("com.tencent.mm").apply {
+ ActivityUtils.startActivity(this)
+ }
+ }
+ )
+ }
+}
diff --git a/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/battery/BatteryActivity.kt b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/battery/BatteryActivity.kt
new file mode 100644
index 0000000000..8e04163c4f
--- /dev/null
+++ b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/battery/BatteryActivity.kt
@@ -0,0 +1,56 @@
+package com.blankj.subutil.pkg.feature.battery
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import com.blankj.common.activity.CommonActivity
+import com.blankj.common.item.CommonItem
+import com.blankj.common.item.CommonItemTitle
+import com.blankj.subutil.pkg.R
+import com.blankj.subutil.util.BatteryUtils
+import com.blankj.utilcode.util.CollectionUtils
+import com.blankj.utilcode.util.ToastUtils
+
+/**
+ * ```
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 20/03/31
+ * desc : demo about Battery
+ * ```
+ */
+class BatteryActivity : CommonActivity(), BatteryUtils.OnBatteryStatusChangedListener {
+
+ private val titleItem: CommonItemTitle = CommonItemTitle("", true);
+
+ companion object {
+ fun start(context: Context) {
+ val starter = Intent(context, BatteryActivity::class.java)
+ context.startActivity(starter)
+ }
+ }
+
+ override fun bindTitleRes(): Int {
+ return R.string.demo_battery
+ }
+
+ override fun bindItems(): MutableList> {
+ return CollectionUtils.newArrayList(titleItem)
+ }
+
+ override fun initView(savedInstanceState: Bundle?, contentView: View?) {
+ super.initView(savedInstanceState, contentView)
+ BatteryUtils.registerBatteryStatusChangedListener(this)
+ }
+
+ override fun onBatteryStatusChanged(status: BatteryUtils.Status) {
+ titleItem.title = status.toString()
+ ToastUtils.showShort(status.toString())
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ BatteryUtils.unregisterBatteryStatusChangedListener(this)
+ }
+}
diff --git a/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/country/CountryActivity.kt b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/country/CountryActivity.kt
new file mode 100644
index 0000000000..c2daa6ef50
--- /dev/null
+++ b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/country/CountryActivity.kt
@@ -0,0 +1,41 @@
+package com.blankj.subutil.pkg.feature.country
+
+import android.content.Context
+import android.content.Intent
+import com.blankj.common.activity.CommonActivity
+import com.blankj.common.item.CommonItem
+import com.blankj.common.item.CommonItemTitle
+import com.blankj.subutil.pkg.R
+import com.blankj.subutil.util.CountryUtils
+import com.blankj.utilcode.util.CollectionUtils
+
+/**
+ * ```
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 17/02/01
+ * desc : demo about Country
+ * ```
+ */
+class CountryActivity : CommonActivity() {
+
+ companion object {
+ fun start(context: Context) {
+ val starter = Intent(context, CountryActivity::class.java)
+ context.startActivity(starter)
+ }
+ }
+
+ override fun bindTitleRes(): Int {
+ return R.string.demo_country
+ }
+
+ override fun bindItems(): MutableList> {
+ return CollectionUtils.newArrayList(
+ CommonItemTitle("getCountryCodeBySim", CountryUtils.getCountryCodeBySim("Default")),
+ CommonItemTitle("getCountryCodeByLanguage", CountryUtils.getCountryCodeByLanguage("Default")),
+ CommonItemTitle("getCountryBySim", CountryUtils.getCountryBySim()),
+ CommonItemTitle("getCountryByLanguage", CountryUtils.getCountryByLanguage())
+ )
+ }
+}
diff --git a/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/dangerous/DangerousActivity.kt b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/dangerous/DangerousActivity.kt
new file mode 100644
index 0000000000..cc5c60f470
--- /dev/null
+++ b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/dangerous/DangerousActivity.kt
@@ -0,0 +1,123 @@
+package com.blankj.subutil.pkg.feature.dangerous
+
+import android.content.Context
+import android.content.Intent
+import com.blankj.common.activity.CommonActivity
+import com.blankj.common.helper.PermissionHelper
+import com.blankj.common.item.CommonItem
+import com.blankj.common.item.CommonItemClick
+import com.blankj.common.item.CommonItemSwitch
+import com.blankj.subutil.pkg.Config
+import com.blankj.subutil.pkg.R
+import com.blankj.subutil.util.DangerousUtils
+import com.blankj.utilcode.constant.PermissionConstants
+import com.blankj.utilcode.util.*
+
+/**
+ * ```
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 17/02/01
+ * desc : demo about dangerous
+ * ```
+ */
+class DangerousActivity : CommonActivity() {
+
+ companion object {
+ fun start(context: Context) {
+ PermissionHelper.request(context, object : PermissionUtils.SimpleCallback {
+ override fun onGranted() {
+ val starter = Intent(context, DangerousActivity::class.java)
+ context.startActivity(starter)
+ }
+
+ override fun onDenied() {
+ }
+ }, PermissionConstants.STORAGE, PermissionConstants.SMS)
+ }
+ }
+
+ private val listener = object : OnReleasedListener {
+ override fun onReleased() {
+ if (DangerousUtils.installAppSilent(Config.TEST_APK_PATH)) {
+ ToastUtils.showShort(R.string.dangerous_install_successfully)
+ } else {
+ ToastUtils.showShort(R.string.dangerous_install_unsuccessfully)
+ }
+ }
+ }
+
+ override fun bindTitleRes(): Int {
+ return R.string.demo_dangerous
+ }
+
+ override fun bindItems(): MutableList> {
+ return CollectionUtils.newArrayList(
+ CommonItemClick(R.string.dangerous_install_silent) {
+ if (AppUtils.isAppInstalled(Config.TEST_PKG)) {
+ ToastUtils.showShort(R.string.dangerous_app_install_tips)
+ } else {
+ if (!FileUtils.isFileExists(Config.TEST_APK_PATH)) {
+ ReleaseInstallApkTask(listener).execute()
+ } else {
+ listener.onReleased()
+ }
+ }
+ },
+ CommonItemClick(R.string.dangerous_uninstall_silent) {
+ if (AppUtils.isAppInstalled(Config.TEST_PKG)) {
+ if (DangerousUtils.uninstallAppSilent(Config.TEST_PKG, false)) {
+ ToastUtils.showShort(R.string.dangerous_uninstall_successfully)
+ } else {
+ ToastUtils.showShort(R.string.dangerous_uninstall_unsuccessfully)
+ }
+ } else {
+ ToastUtils.showShort(R.string.dangerous_app_uninstall_tips)
+ }
+ },
+ CommonItemClick(R.string.dangerous_shutdown) {
+ ToastUtils.showShort(DangerousUtils.shutdown().toString())
+ },
+ CommonItemClick(R.string.dangerous_reboot) {
+ ToastUtils.showShort(DangerousUtils.reboot().toString())
+ },
+ CommonItemClick(R.string.dangerous_reboot_to_recovery) {
+ ToastUtils.showShort(DangerousUtils.reboot2Recovery().toString())
+ },
+ CommonItemClick(R.string.dangerous_reboot_to_bootloader) {
+ ToastUtils.showShort(DangerousUtils.reboot2Bootloader().toString())
+ },
+ CommonItemSwitch(
+ R.string.dangerous_data_enabled,
+ { NetworkUtils.getMobileDataEnabled() },
+ {
+ if (AppUtils.isAppSystem()) {
+ DangerousUtils.setMobileDataEnabled(it)
+ }
+ }
+ ),
+ CommonItemClick(R.string.dangerous_send_sms_silent) {
+ DangerousUtils.sendSmsSilent("10000", "sendSmsSilent")
+ }
+ )
+ }
+}
+
+class ReleaseInstallApkTask(private val mListener: OnReleasedListener) : ThreadUtils.SimpleTask() {
+
+ override fun doInBackground() {
+ ResourceUtils.copyFileFromAssets("test_install", Config.TEST_APK_PATH)
+ }
+
+ override fun onSuccess(result: Unit) {
+ mListener.onReleased()
+ }
+
+ fun execute() {
+ ThreadUtils.executeByIo(this)
+ }
+}
+
+interface OnReleasedListener {
+ fun onReleased()
+}
\ No newline at end of file
diff --git a/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/location/LocationActivity.kt b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/location/LocationActivity.kt
new file mode 100755
index 0000000000..8f4217c96c
--- /dev/null
+++ b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/location/LocationActivity.kt
@@ -0,0 +1,100 @@
+package com.blankj.subutil.pkg.feature.location
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.IBinder
+import com.blankj.common.activity.CommonActivity
+import com.blankj.common.helper.PermissionHelper
+import com.blankj.common.item.CommonItem
+import com.blankj.common.item.CommonItemTitle
+import com.blankj.subutil.pkg.R
+import com.blankj.utilcode.constant.PermissionConstants
+import com.blankj.utilcode.util.CollectionUtils
+import com.blankj.utilcode.util.PermissionUtils
+
+/**
+ * ```
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 2016/10/13
+ * desc : demo about LocationUtils
+ * ```
+ */
+class LocationActivity : CommonActivity() {
+
+ private var lastLatitude: String = "unknown"
+ private var lastLongitude: String = "unknown"
+ private var latitude: String = "unknown"
+ private var longitude: String = "unknown"
+ private var country: String = "unknown"
+ private var locality: String = "unknown"
+ private var street: String = "unknown"
+
+ companion object {
+ fun start(context: Context) {
+ PermissionHelper.request(context, object : PermissionUtils.SimpleCallback {
+ override fun onGranted() {
+ val starter = Intent(context, LocationActivity::class.java)
+ context.startActivity(starter)
+ }
+
+ override fun onDenied() {
+ }
+ }, PermissionConstants.LOCATION)
+ }
+ }
+
+ private lateinit var mLocationService: LocationService
+
+ private var conn: ServiceConnection = object : ServiceConnection {
+ override fun onServiceDisconnected(name: ComponentName) {}
+
+ override fun onServiceConnected(name: ComponentName, service: IBinder) {
+ mLocationService = (service as LocationService.LocationBinder).service
+ mLocationService.setOnGetLocationListener(object : LocationService.OnGetLocationListener {
+ override fun getLocation(lastLatitude: String, lastLongitude: String, latitude: String,
+ longitude: String, country: String, locality: String, street: String) {
+ this@LocationActivity.apply {
+ this.lastLatitude = lastLatitude
+ this.lastLongitude = lastLongitude
+ this.latitude = latitude
+ this.longitude = longitude
+ this.country = country
+ this.locality = locality
+ this.street = street
+ }
+ runOnUiThread {
+ itemsView.updateItems(bindItems())
+ }
+ }
+ })
+ }
+ }
+
+ override fun bindTitleRes(): Int {
+ return R.string.demo_location
+ }
+
+ override fun bindItems(): MutableList> {
+ return CollectionUtils.newArrayList(
+ CommonItemTitle("lastLatitude", lastLatitude),
+ CommonItemTitle("lastLongitude", lastLongitude),
+ CommonItemTitle("latitude", latitude),
+ CommonItemTitle("longitude", longitude),
+ CommonItemTitle("getCountryName", country),
+ CommonItemTitle("getLocality", locality),
+ CommonItemTitle("getStreet", street)
+ )
+ }
+
+ override fun doBusiness() {
+ bindService(Intent(this, LocationService::class.java), conn, Context.BIND_AUTO_CREATE)
+ }
+
+ override fun onDestroy() {
+ unbindService(conn)
+ super.onDestroy()
+ }
+}
diff --git a/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/location/LocationService.kt b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/location/LocationService.kt
new file mode 100755
index 0000000000..8322500d63
--- /dev/null
+++ b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/location/LocationService.kt
@@ -0,0 +1,95 @@
+package com.blankj.subutil.pkg.feature.location
+
+import android.app.Service
+import android.content.Intent
+import android.location.Location
+import android.os.Binder
+import android.os.Bundle
+import android.os.IBinder
+import android.os.Looper
+
+import com.blankj.subutil.util.LocationUtils
+import com.blankj.utilcode.util.ToastUtils
+
+/**
+ * ```
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 2016/11/21
+ * desc : demo about LocationUtils
+ * ```
+ */
+class LocationService : Service() {
+
+ private var isSuccess: Boolean = false
+ private var lastLatitude = "loading..."
+ private var lastLongitude = "loading..."
+ private var latitude = "loading..."
+ private var longitude = "loading..."
+ private var country = "loading..."
+ private var locality = "loading..."
+ private var street = "loading..."
+
+ private var mOnGetLocationListener: OnGetLocationListener? = null
+
+ private val mOnLocationChangeListener = object : LocationUtils.OnLocationChangeListener {
+ override fun getLastKnownLocation(location: Location) {
+ lastLatitude = location.latitude.toString()
+ lastLongitude = location.longitude.toString()
+ mOnGetLocationListener?.getLocation(lastLatitude, lastLongitude, latitude, longitude, country, locality, street)
+ }
+
+ override fun onLocationChanged(location: Location) {
+ latitude = location.latitude.toString()
+ longitude = location.longitude.toString()
+ mOnGetLocationListener?.getLocation(lastLatitude, lastLongitude, latitude, longitude, country, locality, street)
+ country = LocationUtils.getCountryName(java.lang.Double.parseDouble(latitude), java.lang.Double.parseDouble(longitude))
+ locality = LocationUtils.getLocality(java.lang.Double.parseDouble(latitude), java.lang.Double.parseDouble(longitude))
+ street = LocationUtils.getStreet(java.lang.Double.parseDouble(latitude), java.lang.Double.parseDouble(longitude))
+ mOnGetLocationListener?.getLocation(lastLatitude, lastLongitude, latitude, longitude, country, locality, street)
+ }
+
+ override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {}
+ }
+
+ fun setOnGetLocationListener(onGetLocationListener: OnGetLocationListener) {
+ mOnGetLocationListener = onGetLocationListener
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ Thread(Runnable {
+ Looper.prepare()
+ isSuccess = LocationUtils.register(0, 0, mOnLocationChangeListener)
+ if (isSuccess) ToastUtils.showShort("init success")
+ Looper.loop()
+ }).start()
+ }
+
+ override fun onBind(intent: Intent): IBinder? {
+ return LocationBinder()
+ }
+
+ inner class LocationBinder : Binder() {
+ val service: LocationService
+ get() = this@LocationService
+ }
+
+ override fun onDestroy() {
+ LocationUtils.unregister()
+ // 一定要制空,否则内存泄漏
+ mOnGetLocationListener = null
+ super.onDestroy()
+ }
+
+ /**
+ * 获取位置监听器
+ */
+ interface OnGetLocationListener {
+ fun getLocation(
+ lastLatitude: String, lastLongitude: String,
+ latitude: String, longitude: String,
+ country: String, locality: String, street: String
+ )
+ }
+}
diff --git a/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/pinyin/PinyinActivity.kt b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/pinyin/PinyinActivity.kt
new file mode 100644
index 0000000000..648dcb295d
--- /dev/null
+++ b/feature/subutil/pkg/src/main/java/com/blankj/subutil/pkg/feature/pinyin/PinyinActivity.kt
@@ -0,0 +1,56 @@
+package com.blankj.subutil.pkg.feature.pinyin
+
+import android.content.Context
+import android.content.Intent
+import com.blankj.common.activity.CommonActivity
+import com.blankj.common.item.CommonItem
+import com.blankj.common.item.CommonItemTitle
+import com.blankj.subutil.pkg.R
+import com.blankj.subutil.util.PinyinUtils
+import com.blankj.utilcode.util.CollectionUtils
+
+/**
+ * ```
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 17/02/01
+ * desc : demo about PinyinUtils
+ * ```
+ */
+class PinyinActivity : CommonActivity() {
+
+ companion object {
+ fun start(context: Context) {
+ val starter = Intent(context, PinyinActivity::class.java)
+ context.startActivity(starter)
+ }
+ }
+
+ override fun bindTitleRes(): Int {
+ return R.string.demo_pinyin
+ }
+
+ override fun bindItems(): MutableList> {
+ val surnames = "乐乘乜仇会便区单参句召员宓弗折曾朴查洗盖祭种秘繁缪能蕃覃解谌适都阿难黑"
+ val size = surnames.length
+ val sb = StringBuilder("澹台: " + PinyinUtils.getSurnamePinyin("澹台")
+ + "\n尉迟: " + PinyinUtils.getSurnamePinyin("尉迟")
+ + "\n万俟: " + PinyinUtils.getSurnamePinyin("万俟")
+ + "\n单于: " + PinyinUtils.getSurnamePinyin("单于"))
+ for (i in 0 until size) {
+ val surname = surnames[i].toString()
+ sb.append(String.format(
+ "\n%s 正确: %-8s 错误: %-8s",
+ surname,
+ PinyinUtils.getSurnamePinyin(surname),
+ PinyinUtils.ccs2Pinyin(surname)
+ ))
+ }
+ return CollectionUtils.newArrayList(
+ CommonItemTitle("汉字转拼音", PinyinUtils.ccs2Pinyin("汉字转拼音", " ")),
+ CommonItemTitle("获取首字母", PinyinUtils.getPinyinFirstLetters("获取首字母", " ")),
+ CommonItemTitle("测试姓氏", sb.toString())
+
+ )
+ }
+}
\ No newline at end of file
diff --git a/feature/subutil/pkg/src/main/res/values/strings.xml b/feature/subutil/pkg/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..e758c294a0
--- /dev/null
+++ b/feature/subutil/pkg/src/main/res/values/strings.xml
@@ -0,0 +1,29 @@
+
+
+ App Store Demo
+ Battery Demo
+ Country Demo
+ Dangerous Demo
+ LocationUtils Demo
+ PinyinUtils Demo
+
+
+ Go System App Store In WeChat Page
+
+ Install Test App Silently
+ Uninstall App Silently
+ Test app have installed
+ Install successfully
+ Install unsuccessfully
+ Please install test app first
+ Uninstall successfully
+ Uninstall unsuccessfully
+ Shutdown
+ Reboot
+ Reboot To Recovery
+ Reboot To Bootloader
+ Send SMS Silent
+ Mobile Data Enabled
+
+
+
\ No newline at end of file
diff --git a/feature/utilcode/app/.gitignore b/feature/utilcode/app/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/feature/utilcode/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/feature/utilcode/app/build.gradle b/feature/utilcode/app/build.gradle
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/feature/utilcode/app/proguard-rules.pro b/feature/utilcode/app/proguard-rules.pro
new file mode 100644
index 0000000000..f1b424510d
--- /dev/null
+++ b/feature/utilcode/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/feature/utilcode/app/src/main/AndroidManifest.xml b/feature/utilcode/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..34308fcb79
--- /dev/null
+++ b/feature/utilcode/app/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feature/utilcode/app/src/main/java/com/blankj/utilcode/app/UtilCodeApp.kt b/feature/utilcode/app/src/main/java/com/blankj/utilcode/app/UtilCodeApp.kt
new file mode 100644
index 0000000000..42d2909041
--- /dev/null
+++ b/feature/utilcode/app/src/main/java/com/blankj/utilcode/app/UtilCodeApp.kt
@@ -0,0 +1,30 @@
+package com.blankj.utilcode.app
+
+import com.blankj.common.CommonApplication
+import com.blankj.utilcode.util.Utils
+
+
+/**
+ * ```
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 2016/10/12
+ * desc : app about utils
+ * ```
+ */
+class UtilCodeApp : CommonApplication() {
+
+ companion object {
+ lateinit var instance: UtilCodeApp
+ private set
+ }
+
+ override fun onCreate() {
+ Utils.init(this)
+ super.onCreate()
+ instance = this
+// BusUtils.register("com.blankj.androidutilcode")
+ }
+}
+
+
diff --git a/feature/utilcode/export/.gitignore b/feature/utilcode/export/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/feature/utilcode/export/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/feature/utilcode/export/build.gradle b/feature/utilcode/export/build.gradle
new file mode 100644
index 0000000000..30e59272ee
--- /dev/null
+++ b/feature/utilcode/export/build.gradle
@@ -0,0 +1,8 @@
+ext {
+ groupId = Config.modules.feature_utilcode_export.groupId
+ artifactId = Config.modules.feature_utilcode_export.artifactId
+ version = Config.modules.feature_utilcode_export.version
+ website = "https://github.com/Blankj/AndroidUtilCode"
+}
+//apply from: "${rootDir.path}/config/publish.gradle"
+//./gradlew :feature_utilcode_export:mavenLocal // 上传到本地 mavenLocal
\ No newline at end of file
diff --git a/feature/utilcode/export/proguard-rules.pro b/feature/utilcode/export/proguard-rules.pro
new file mode 100644
index 0000000000..f1b424510d
--- /dev/null
+++ b/feature/utilcode/export/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/feature/utilcode/export/src/main/AndroidManifest.xml b/feature/utilcode/export/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..9bbbcc3210
--- /dev/null
+++ b/feature/utilcode/export/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/feature/utilcode/export/src/main/java/com/blankj/utilcode/export/api/UtilCodeApi.java b/feature/utilcode/export/src/main/java/com/blankj/utilcode/export/api/UtilCodeApi.java
new file mode 100644
index 0000000000..ea7d5291be
--- /dev/null
+++ b/feature/utilcode/export/src/main/java/com/blankj/utilcode/export/api/UtilCodeApi.java
@@ -0,0 +1,26 @@
+package com.blankj.utilcode.export.api;
+
+import android.content.Context;
+
+import com.blankj.utilcode.util.ApiUtils;
+
+
+/**
+ *
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 2019/07/01
+ * desc :
+ *
+ */
+public abstract class UtilCodeApi extends ApiUtils.BaseApi {
+
+ public abstract void startUtilCodeActivity(Context context);
+
+ public abstract void testCallback(Callback callback);
+
+ public interface Callback {
+ void call();
+ }
+
+}
\ No newline at end of file
diff --git a/feature/utilcode/pkg/.gitignore b/feature/utilcode/pkg/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/feature/utilcode/pkg/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/feature/utilcode/pkg/build.gradle b/feature/utilcode/pkg/build.gradle
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/feature/utilcode/pkg/proguard-rules.pro b/feature/utilcode/pkg/proguard-rules.pro
new file mode 100644
index 0000000000..f1b424510d
--- /dev/null
+++ b/feature/utilcode/pkg/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/feature/utilcode/pkg/src/main/AndroidManifest.xml b/feature/utilcode/pkg/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..8da0667a23
--- /dev/null
+++ b/feature/utilcode/pkg/src/main/AndroidManifest.xml
@@ -0,0 +1,284 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/feature/utilcode/pkg/src/main/assets/fonts/dnmbhs.ttf b/feature/utilcode/pkg/src/main/assets/fonts/dnmbhs.ttf
new file mode 100644
index 0000000000..8ac8dadba1
Binary files /dev/null and b/feature/utilcode/pkg/src/main/assets/fonts/dnmbhs.ttf differ
diff --git a/feature/utilcode/pkg/src/main/assets/test/sub/test.txt b/feature/utilcode/pkg/src/main/assets/test/sub/test.txt
new file mode 100644
index 0000000000..f7bc1649eb
--- /dev/null
+++ b/feature/utilcode/pkg/src/main/assets/test/sub/test.txt
@@ -0,0 +1,2 @@
+1st line
+2nd line
\ No newline at end of file
diff --git a/feature/utilcode/pkg/src/main/assets/test/test.txt b/feature/utilcode/pkg/src/main/assets/test/test.txt
new file mode 100644
index 0000000000..f7bc1649eb
--- /dev/null
+++ b/feature/utilcode/pkg/src/main/assets/test/test.txt
@@ -0,0 +1,2 @@
+1st line
+2nd line
\ No newline at end of file
diff --git a/feature/utilcode/pkg/src/main/java/com/blankj/utilcode/pkg/Config.kt b/feature/utilcode/pkg/src/main/java/com/blankj/utilcode/pkg/Config.kt
new file mode 100644
index 0000000000..51bb0b7e66
--- /dev/null
+++ b/feature/utilcode/pkg/src/main/java/com/blankj/utilcode/pkg/Config.kt
@@ -0,0 +1,19 @@
+package com.blankj.utilcode.pkg
+
+import com.blankj.utilcode.util.PathUtils
+
+/**
+ * ```
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 2017/05/10
+ * desc : config about constants
+ * ```
+ */
+object Config {
+ val FILE_SEP = System.getProperty("file.separator")
+ val LINE_SEP = System.getProperty("line.separator")
+ const val TEST_PKG = "com.blankj.testinstall"
+ val CACHE_PATH = PathUtils.getCachePathExternalFirst() + FILE_SEP
+ val TEST_APK_PATH: String = CACHE_PATH + "test_install.apk"
+}
diff --git a/feature/utilcode/pkg/src/main/java/com/blankj/utilcode/pkg/UtilCodeApiImpl.java b/feature/utilcode/pkg/src/main/java/com/blankj/utilcode/pkg/UtilCodeApiImpl.java
new file mode 100644
index 0000000000..076858b3d1
--- /dev/null
+++ b/feature/utilcode/pkg/src/main/java/com/blankj/utilcode/pkg/UtilCodeApiImpl.java
@@ -0,0 +1,31 @@
+package com.blankj.utilcode.pkg;
+
+import android.content.Context;
+
+import com.blankj.utilcode.export.api.UtilCodeApi;
+import com.blankj.utilcode.pkg.feature.CoreUtilActivity;
+import com.blankj.utilcode.util.ApiUtils;
+
+/**
+ *
+ * author: Blankj
+ * blog : http://blankj.com
+ * time : 2019/07/01
+ * desc :
+ *