diff --git a/app/src/main/java/com/blankj/androidutilcode/base/BaseActivity.java b/app/src/main/java/com/blankj/androidutilcode/base/BaseActivity.java index b09746f9e75447dbd0ea454c42cca7cd21affa33..647fae7c92a2d05ee22c9b450f3d562d902baaf5 100644 --- a/app/src/main/java/com/blankj/androidutilcode/base/BaseActivity.java +++ b/app/src/main/java/com/blankj/androidutilcode/base/BaseActivity.java @@ -8,8 +8,6 @@ import android.support.v7.app.AppCompatActivity; import android.view.LayoutInflater; import android.view.View; -import com.blankj.utilcode.util.ScreenUtils; - /** *
  *     author: Blankj
diff --git a/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/LogUtils.java b/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/LogUtils.java
index c2b40033a7defec73250c6625171cea08f1a3dce..b44d9c25b89fefd04298b63479a202aa3588a483 100644
--- a/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/LogUtils.java
+++ b/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/LogUtils.java
@@ -400,7 +400,6 @@ public final class LogUtils {
             Log.println(type, tag, msg);
             return;
         }
-        StringBuilder sb = new StringBuilder();
         String[] lines = msg.split(LINE_SEP);
         for (String line : lines) {
             Log.println(type, tag, LEFT_BORDER + line);
@@ -577,16 +576,6 @@ public final class LogUtils {
         return file != null && (file.exists() ? file.isDirectory() : file.mkdirs());
     }
 
-    private static boolean isSpace(final String s) {
-        if (s == null) return true;
-        for (int i = 0, len = s.length(); i < len; ++i) {
-            if (!Character.isWhitespace(s.charAt(i))) {
-                return false;
-            }
-        }
-        return true;
-    }
-
     private static void input2File(final String input, final String filePath) {
         EXECUTOR.execute(new Runnable() {
             @Override
diff --git a/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/LogUtils.kt b/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/LogUtils.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e3041b6b1d649abb5091e423a2403a8d194c2d75
--- /dev/null
+++ b/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/LogUtils.kt
@@ -0,0 +1,1021 @@
+@file:JvmName("LogUtils")
+
+package com.blankj.utilcode.util
+
+import android.content.ClipData
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Bundle
+import android.os.Environment
+import android.support.annotation.IntDef
+import android.support.annotation.IntRange
+import android.support.annotation.RequiresApi
+import android.support.v4.util.SimpleArrayMap
+import android.util.Log
+import org.json.JSONArray
+import org.json.JSONException
+import org.json.JSONObject
+import java.io.*
+import java.lang.reflect.ParameterizedType
+import java.lang.reflect.Type
+import java.net.UnknownHostException
+import java.text.ParseException
+import java.text.SimpleDateFormat
+import java.util.*
+import java.util.concurrent.Executors
+import javax.xml.transform.OutputKeys
+import javax.xml.transform.TransformerFactory
+import javax.xml.transform.stream.StreamResult
+import javax.xml.transform.stream.StreamSource
+
+
+const val V = Log.VERBOSE
+const val D = Log.DEBUG
+const val I = Log.INFO
+const val W = Log.WARN
+const val E = Log.ERROR
+const val A = Log.ASSERT
+
+@IntDef(V, D, I, W, E, A)
+@Retention(AnnotationRetention.SOURCE)
+annotation class TYPE
+
+private val T = charArrayOf('V', 'D', 'I', 'W', 'E', 'A')
+
+private val FILE = 0x10
+private val JSON = 0x20
+private val XML = 0x30
+
+private const val TOP_CORNER = "┌"
+private const val MIDDLE_CORNER = "├"
+private const val LEFT_BORDER = "│ "
+private const val BOTTOM_CORNER = "└"
+private const val SIDE_DIVIDER = "────────────────────────────────────────────────────────"
+private const val MIDDLE_DIVIDER = "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄"
+private const val TOP_BORDER = TOP_CORNER + SIDE_DIVIDER + SIDE_DIVIDER
+private const val MIDDLE_BORDER = MIDDLE_CORNER + MIDDLE_DIVIDER + MIDDLE_DIVIDER
+private const val BOTTOM_BORDER = BOTTOM_CORNER + SIDE_DIVIDER + SIDE_DIVIDER
+private const val MAX_LEN = 3000
+private const val NOTHING = "log nothing"
+private const val NULL = "null"
+private const val ARGS = "args"
+private const val PLACEHOLDER = " "
+
+private val FILE_SEP = System.getProperty("file.separator")
+private val LINE_SEP = System.getProperty("line.separator")
+private val FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ", Locale.getDefault())
+private val CONFIG = Config()
+
+private val EXECUTOR = Executors.newSingleThreadExecutor()
+
+private val I_FORMATTER_MAP = SimpleArrayMap, IFormatter<*>>()
+
+fun getConfig(): Config {
+    return CONFIG
+}
+
+fun v(vararg contents: Any) {
+    log(V, CONFIG.mGlobalTag, *contents)
+}
+
+fun vTag(tag: String, vararg contents: Any) {
+    log(V, tag, *contents)
+}
+
+fun d(vararg contents: Any) {
+    log(D, CONFIG.mGlobalTag, *contents)
+}
+
+fun dTag(tag: String, vararg contents: Any) {
+    log(D, tag, *contents)
+}
+
+fun i(vararg contents: Any) {
+    log(I, CONFIG.mGlobalTag, *contents)
+}
+
+fun iTag(tag: String, vararg contents: Any) {
+    log(I, tag, *contents)
+}
+
+fun w(vararg contents: Any) {
+    log(W, CONFIG.mGlobalTag, *contents)
+}
+
+fun wTag(tag: String, vararg contents: Any) {
+    log(W, tag, *contents)
+}
+
+fun e(vararg contents: Any) {
+    log(E, CONFIG.mGlobalTag, *contents)
+}
+
+fun eTag(tag: String, vararg contents: Any) {
+    log(E, tag, *contents)
+}
+
+fun a(vararg contents: Any) {
+    log(A, CONFIG.mGlobalTag, *contents)
+}
+
+fun aTag(tag: String, vararg contents: Any) {
+    log(A, tag, *contents)
+}
+
+fun file(content: Any) {
+    log(FILE or D, CONFIG.mGlobalTag, content)
+}
+
+fun file(@TYPE type: Int, content: Any) {
+    log(FILE or type, CONFIG.mGlobalTag, content)
+}
+
+fun file(tag: String, content: Any) {
+    log(FILE or D, tag, content)
+}
+
+fun file(@TYPE type: Int, tag: String, content: Any) {
+    log(FILE or type, tag, content)
+}
+
+fun json(content: String) {
+    log(JSON or D, CONFIG.mGlobalTag, content)
+}
+
+fun json(@TYPE type: Int, content: String) {
+    log(JSON or type, CONFIG.mGlobalTag, content)
+}
+
+fun json(tag: String, content: String) {
+    log(JSON or D, tag, content)
+}
+
+fun json(@TYPE type: Int, tag: String, content: String) {
+    log(JSON or type, tag, content)
+}
+
+fun xml(content: String) {
+    log(XML or D, CONFIG.mGlobalTag, content)
+}
+
+fun xml(@TYPE type: Int, content: String) {
+    log(XML or type, CONFIG.mGlobalTag, content)
+}
+
+fun xml(tag: String, content: String) {
+    log(XML or D, tag, content)
+}
+
+fun xml(@TYPE type: Int, tag: String, content: String) {
+    log(XML or type, tag, content)
+}
+
+fun log(type: Int, tag: String?, vararg contents: Any) {
+    if (!CONFIG.mLogSwitch || !CONFIG.mLog2ConsoleSwitch && !CONFIG.mLog2FileSwitch) return
+    val type_low = type and 0x0f
+    val type_high = type and 0xf0
+    if (type_low < CONFIG.mConsoleFilter && type_low < CONFIG.mFileFilter) return
+    val tagHead = processTagAndHead(tag)
+    val body = processBody(type_high, *contents)
+    if (CONFIG.mLog2ConsoleSwitch && type_low >= CONFIG.mConsoleFilter && type_high != FILE) {
+        print2Console(type_low, tagHead.tag, tagHead.consoleHead, body)
+    }
+    if ((CONFIG.mLog2FileSwitch || type_high == FILE) && type_low >= CONFIG.mFileFilter) {
+        print2File(type_low, tagHead.tag, tagHead.fileHead + body)
+    }
+}
+
+private fun processTagAndHead(tag: String?): TagHead {
+    var tag = tag
+    if (!CONFIG.mTagIsSpace && !CONFIG.mLogHeadSwitch) {
+        tag = CONFIG.mGlobalTag
+    } else {
+        val stackTrace = Throwable().stackTrace
+        val stackIndex = 3 + CONFIG.mStackOffset
+        if (stackIndex >= stackTrace.size) {
+            val targetElement = stackTrace[3]
+            val fileName = getFileName(targetElement)
+            if (CONFIG.mTagIsSpace && isSpace(tag)) {
+                val index = fileName.indexOf('.')// Use proguard may not find '.'.
+                tag = if (index == -1) fileName else fileName.substring(0, index)
+            }
+            return TagHead(tag, null, ": ")
+        }
+        var targetElement = stackTrace[stackIndex]
+        val fileName = getFileName(targetElement)
+        if (CONFIG.mTagIsSpace && isSpace(tag)) {
+            val index = fileName.indexOf('.')// Use proguard may not find '.'.
+            tag = if (index == -1) fileName else fileName.substring(0, index)
+        }
+        if (CONFIG.mLogHeadSwitch) {
+            val tName = Thread.currentThread().name
+            val head = Formatter()
+                    .format("%s, %s.%s(%s:%d)",
+                            tName,
+                            targetElement.className,
+                            targetElement.methodName,
+                            fileName,
+                            targetElement.lineNumber)
+                    .toString()
+            val fileHead = " [$head]: "
+            if (CONFIG.mStackDeep <= 1) {
+                return TagHead(tag, arrayOf(head), fileHead)
+            } else {
+                val consoleHead = arrayOfNulls(Math.min(
+                        CONFIG.mStackDeep,
+                        stackTrace.size - stackIndex
+                ))
+                consoleHead[0] = head
+                val spaceLen = tName.length + 2
+                val space = Formatter().format("%" + spaceLen + "s", "").toString()
+                var i = 1
+                val len = consoleHead.size
+                while (i < len) {
+                    targetElement = stackTrace[i + stackIndex]
+                    consoleHead[i] = Formatter()
+                            .format("%s%s.%s(%s:%d)",
+                                    space,
+                                    targetElement.className,
+                                    targetElement.methodName,
+                                    getFileName(targetElement),
+                                    targetElement.lineNumber)
+                            .toString()
+                    ++i
+                }
+                return TagHead(tag, consoleHead, fileHead)
+            }
+        }
+    }
+    return TagHead(tag, null, ": ")
+}
+
+private fun getFileName(targetElement: StackTraceElement): String {
+    val fileName = targetElement.fileName
+    if (fileName != null) return fileName
+    // If name of file is null, should add
+    // "-keepattributes SourceFile,LineNumberTable" in proguard file.
+    var className = targetElement.className
+    val classNameInfo = className.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+    if (classNameInfo.size > 0) {
+        className = classNameInfo[classNameInfo.size - 1]
+    }
+    val index = className.indexOf('$')
+    if (index != -1) {
+        className = className.substring(0, index)
+    }
+    return "$className.java"
+}
+
+private fun processBody(type: Int, vararg contents: Any): String {
+    var body = NULL
+    if (contents != null) {
+        if (contents.size == 1) {
+            body = formatObject(type, contents[0])
+        } else {
+            val sb = StringBuilder()
+            var i = 0
+            val len = contents.size
+            while (i < len) {
+                val content = contents[i]
+                sb.append(ARGS)
+                        .append("[")
+                        .append(i)
+                        .append("]")
+                        .append(" = ")
+                        .append(formatObject(content))
+                        .append(LINE_SEP)
+                ++i
+            }
+            body = sb.toString()
+        }
+    }
+    return if (body.length == 0) NOTHING else body
+}
+
+private fun formatObject(type: Int, obj: Any?): String {
+    if (obj == null) return NULL
+    if (type == JSON) return LogFormatter.formatJson(obj.toString())
+    return if (type == XML) LogFormatter.formatXml(obj.toString()) else formatObject(obj)
+}
+
+private fun formatObject(obj: Any?): String {
+    if (obj == null) return NULL
+    if (!I_FORMATTER_MAP.isEmpty) {
+        val iFormatter = I_FORMATTER_MAP.get(getClassFromObject(obj))
+        if (iFormatter != null) {
+
+            return iFormatter.format(obj)
+        }
+    }
+    if (obj.javaClass.isArray) return LogFormatter.array2String(obj)
+    if (obj is Throwable) return LogFormatter.throwable2String(obj as Throwable?)
+    if (obj is Bundle) return LogFormatter.bundle2String((obj as Bundle?)!!)
+    return if (obj is Intent) LogFormatter.intent2String((obj as Intent?)!!) else obj.toString()
+}
+
+private fun print2Console(type: Int,
+                          tag: String,
+                          head: Array,
+                          msg: String) {
+    if (CONFIG.mSingleTagSwitch) {
+        printSingleTagMsg(type, tag, processSingleTagMsg(type, tag, head, msg))
+    } else {
+        printBorder(type, tag, true)
+        printHead(type, tag, head)
+        printMsg(type, tag, msg)
+        printBorder(type, tag, false)
+    }
+}
+
+private fun printBorder(type: Int, tag: String, isTop: Boolean) {
+    if (CONFIG.mLogBorderSwitch) {
+        Log.println(type, tag, if (isTop) TOP_BORDER else BOTTOM_BORDER)
+    }
+}
+
+private fun printHead(type: Int, tag: String, head: Array?) {
+    if (head != null) {
+        for (aHead in head) {
+            Log.println(type, tag, if (CONFIG.mLogBorderSwitch) LEFT_BORDER + aHead else aHead)
+        }
+        if (CONFIG.mLogBorderSwitch) Log.println(type, tag, MIDDLE_BORDER)
+    }
+}
+
+private fun printMsg(type: Int, tag: String, msg: String) {
+    val len = msg.length
+    val countOfSub = len / MAX_LEN
+    if (countOfSub > 0) {
+        var index = 0
+        for (i in 0 until countOfSub) {
+            printSubMsg(type, tag, msg.substring(index, index + MAX_LEN))
+            index += MAX_LEN
+        }
+        if (index != len) {
+            printSubMsg(type, tag, msg.substring(index, len))
+        }
+    } else {
+        printSubMsg(type, tag, msg)
+    }
+}
+
+private fun printSubMsg(type: Int, tag: String, msg: String) {
+    if (!CONFIG.mLogBorderSwitch) {
+        Log.println(type, tag, msg)
+        return
+    }
+    val sb = StringBuilder()
+    val lines = msg.split(LINE_SEP.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+    for (line in lines) {
+        Log.println(type, tag, LEFT_BORDER + line)
+    }
+}
+
+private fun processSingleTagMsg(type: Int,
+                                tag: String,
+                                head: Array?,
+                                msg: String): String {
+    val sb = StringBuilder()
+    sb.append(PLACEHOLDER).append(LINE_SEP)
+    if (CONFIG.mLogBorderSwitch) {
+        sb.append(TOP_BORDER).append(LINE_SEP)
+        if (head != null) {
+            for (aHead in head) {
+                sb.append(LEFT_BORDER).append(aHead).append(LINE_SEP)
+            }
+            sb.append(MIDDLE_BORDER).append(LINE_SEP)
+        }
+        for (line in msg.split(LINE_SEP.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
+            sb.append(LEFT_BORDER).append(line).append(LINE_SEP)
+        }
+        sb.append(BOTTOM_BORDER)
+    } else {
+        if (head != null) {
+            for (aHead in head) {
+                sb.append(aHead).append(LINE_SEP)
+            }
+        }
+        sb.append(msg)
+    }
+    return sb.toString()
+}
+
+private fun printSingleTagMsg(type: Int, tag: String, msg: String) {
+    val len = msg.length
+    val countOfSub = len / MAX_LEN
+    if (countOfSub > 0) {
+        if (CONFIG.mLogBorderSwitch) {
+            Log.println(type, tag, msg.substring(0, MAX_LEN) + LINE_SEP + BOTTOM_BORDER)
+            var index = MAX_LEN
+            for (i in 1 until countOfSub) {
+                Log.println(type, tag, PLACEHOLDER + LINE_SEP + TOP_BORDER + LINE_SEP
+                        + LEFT_BORDER + msg.substring(index, index + MAX_LEN)
+                        + LINE_SEP + BOTTOM_BORDER)
+                index += MAX_LEN
+            }
+            if (index != len) {
+                Log.println(type, tag, PLACEHOLDER + LINE_SEP + TOP_BORDER + LINE_SEP
+                        + LEFT_BORDER + msg.substring(index, len))
+            }
+        } else {
+            Log.println(type, tag, msg.substring(0, MAX_LEN))
+            var index = MAX_LEN
+            for (i in 1 until countOfSub) {
+                Log.println(type, tag,
+                        PLACEHOLDER + LINE_SEP + msg.substring(index, index + MAX_LEN))
+                index += MAX_LEN
+            }
+            if (index != len) {
+                Log.println(type, tag, PLACEHOLDER + LINE_SEP + msg.substring(index, len))
+            }
+        }
+    } else {
+        Log.println(type, tag, msg)
+    }
+}
+
+private fun print2File(type: Int, tag: String, msg: String) {
+    val now = Date(System.currentTimeMillis())
+    val format = FORMAT.format(now)
+    val date = format.substring(0, 10)
+    val time = format.substring(11)
+    val fullPath = ((if (CONFIG.mDir == null) CONFIG.mDefaultDir else CONFIG.mDir)
+            + CONFIG.mFilePrefix + "-" + date + ".txt")
+    if (!createOrExistsFile(fullPath)) {
+        Log.e("LogUtils", "create $fullPath failed!")
+        return
+    }
+    val sb = StringBuilder()
+    sb.append(time)
+            .append(T[type - V])
+            .append("/")
+            .append(tag)
+            .append(msg)
+            .append(LINE_SEP)
+    val content = sb.toString()
+    input2File(content, fullPath)
+}
+
+private fun createOrExistsFile(filePath: String): Boolean {
+    val file = File(filePath)
+    if (file.exists()) return file.isFile()
+    if (!createOrExistsDir(file.getParentFile())) return false
+    try {
+        deleteDueLogs(filePath)
+        val isCreate = file.createNewFile()
+        if (isCreate) {
+            printDeviceInfo(filePath)
+        }
+        return isCreate
+    } catch (e: IOException) {
+        e.printStackTrace()
+        return false
+    }
+
+}
+
+private fun deleteDueLogs(filePath: String) {
+    val file = File(filePath)
+    val parentFile = file.getParentFile()
+    val files = parentFile.listFiles(object : FilenameFilter() {
+        fun accept(dir: File, name: String): Boolean {
+            return name.matches(("^" + CONFIG.mFilePrefix + "-[0-9]{4}-[0-9]{2}-[0-9]{2}.txt$").toRegex())
+        }
+    })
+    if (files.size <= 0) return
+    val length = filePath.length
+    val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+    try {
+        val curDay = filePath.substring(length - 14, length - 4)
+        val dueMillis = sdf.parse(curDay).getTime() - CONFIG.mSaveDays * 86400000L
+        for (aFile in files) {
+            val name = aFile.getName()
+            val l = name.length
+            val logDay = name.substring(l - 14, l - 4)
+            if (sdf.parse(logDay).getTime() <= dueMillis) {
+                EXECUTOR.execute(Runnable {
+                    val delete = aFile.delete()
+                    if (!delete) {
+                        Log.e("LogUtils", "delete $aFile failed!")
+                    }
+                })
+            }
+        }
+    } catch (e: ParseException) {
+        e.printStackTrace()
+    }
+
+}
+
+private fun printDeviceInfo(filePath: String) {
+    var versionName = ""
+    var versionCode = 0
+    try {
+        val pi = Utils.getApp()
+                .getPackageManager()
+                .getPackageInfo(Utils.getApp().getPackageName(), 0)
+        if (pi != null) {
+            versionName = pi!!.versionName
+            versionCode = pi!!.versionCode
+        }
+    } catch (e: PackageManager.NameNotFoundException) {
+        e.printStackTrace()
+    }
+
+    val time = filePath.substring(filePath.length - 14, filePath.length - 4)
+    val head = "************* Log Head ****************" +
+            "\nDate of Log        : " + time +
+            "\nDevice Manufacturer: " + Build.MANUFACTURER +
+            "\nDevice Model       : " + Build.MODEL +
+            "\nAndroid Version    : " + Build.VERSION.RELEASE +
+            "\nAndroid SDK        : " + Build.VERSION.SDK_INT +
+            "\nApp VersionName    : " + versionName +
+            "\nApp VersionCode    : " + versionCode +
+            "\n************* Log Head ****************\n\n"
+    input2File(head, filePath)
+}
+
+private fun createOrExistsDir(file: File?): Boolean {
+    return file != null && if (file!!.exists()) file!!.isDirectory() else file!!.mkdirs()
+}
+
+private fun input2File(input: String, filePath: String) {
+    EXECUTOR.execute(Runnable {
+        var bw: BufferedWriter? = null
+        try {
+            bw = BufferedWriter(FileWriter(filePath, true))
+            bw!!.write(input)
+        } catch (e: IOException) {
+            e.printStackTrace()
+            Log.e("LogUtils", "log to $filePath failed!")
+        } finally {
+            try {
+                if (bw != null) {
+                    bw!!.close()
+                }
+            } catch (e: IOException) {
+                e.printStackTrace()
+            }
+
+        }
+    })
+}
+
+class Config internal constructor() {
+    var mDefaultDir: String? = null// The default storage directory of log.
+    var mDir: String? = null       // The storage directory of log.
+    var mFilePrefix = "util"// The file prefix of log.
+    var mLogSwitch = true  // The switch of log.
+    var mLog2ConsoleSwitch = true  // The logcat's switch of log.
+    var mGlobalTag: String? = null  // The global tag of log.
+    var mTagIsSpace = true  // The global tag is space.
+    var mLogHeadSwitch = true  // The head's switch of log.
+    var mLog2FileSwitch = false // The file's switch of log.
+    var mLogBorderSwitch = true  // The border's switch of log.
+    var mSingleTagSwitch = true  // The single tag of log.
+    var mConsoleFilter = V     // The console's filter of log.
+    var mFileFilter = V     // The file's filter of log.
+    var mStackDeep = 1     // The stack's deep of log.
+    var mStackOffset = 0     // The stack's offset of log.
+    var mSaveDays = -1    // The save days of log.
+
+    init {
+        if (mDefaultDir != null) return
+        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && Utils.getApp().getExternalCacheDir() != null)
+            mDefaultDir = Utils.getApp().getExternalCacheDir() + FILE_SEP + "log" + FILE_SEP
+        else {
+            mDefaultDir = Utils.getApp().getCacheDir() + FILE_SEP + "log" + FILE_SEP
+        }
+    }
+
+    fun setLogSwitch(logSwitch: Boolean): Config {
+        mLogSwitch = logSwitch
+        return this
+    }
+
+    fun setConsoleSwitch(consoleSwitch: Boolean): Config {
+        mLog2ConsoleSwitch = consoleSwitch
+        return this
+    }
+
+    fun setGlobalTag(tag: String): Config {
+        if (isSpace(tag)) {
+            mGlobalTag = ""
+            mTagIsSpace = true
+        } else {
+            mGlobalTag = tag
+            mTagIsSpace = false
+        }
+        return this
+    }
+
+    fun setLogHeadSwitch(logHeadSwitch: Boolean): Config {
+        mLogHeadSwitch = logHeadSwitch
+        return this
+    }
+
+    fun setLog2FileSwitch(log2FileSwitch: Boolean): Config {
+        mLog2FileSwitch = log2FileSwitch
+        return this
+    }
+
+    fun setDir(dir: String): Config {
+        if (isSpace(dir)) {
+            mDir = null
+        } else {
+            mDir = if (dir.endsWith(FILE_SEP)) dir else dir + FILE_SEP
+        }
+        return this
+    }
+
+    fun setDir(dir: File?): Config {
+        mDir = if (dir == null) null else dir!!.getAbsolutePath() + FILE_SEP
+        return this
+    }
+
+    fun setFilePrefix(filePrefix: String): Config {
+        if (isSpace(filePrefix)) {
+            mFilePrefix = "util"
+        } else {
+            mFilePrefix = filePrefix
+        }
+        return this
+    }
+
+    fun setBorderSwitch(borderSwitch: Boolean): Config {
+        mLogBorderSwitch = borderSwitch
+        return this
+    }
+
+    fun setSingleTagSwitch(singleTagSwitch: Boolean): Config {
+        mSingleTagSwitch = singleTagSwitch
+        return this
+    }
+
+    fun setConsoleFilter(@TYPE consoleFilter: Int): Config {
+        mConsoleFilter = consoleFilter
+        return this
+    }
+
+    fun setFileFilter(@TYPE fileFilter: Int): Config {
+        mFileFilter = fileFilter
+        return this
+    }
+
+    fun setStackDeep(@IntRange(from = 1) stackDeep: Int): Config {
+        mStackDeep = stackDeep
+        return this
+    }
+
+    fun setStackOffset(@IntRange(from = 0) stackOffset: Int): Config {
+        mStackOffset = stackOffset
+        return this
+    }
+
+    fun setSaveDays(@IntRange(from = 1) saveDays: Int): Config {
+        mSaveDays = saveDays
+        return this
+    }
+
+    fun  addFormatter(iFormatter: IFormatter?): Config {
+        if (iFormatter != null) {
+            I_FORMATTER_MAP.put(getTypeClassFromParadigm(iFormatter), iFormatter)
+        }
+        return this
+    }
+
+    override fun toString(): String {
+        return ("switch: " + mLogSwitch
+                + LINE_SEP + "console: " + mLog2ConsoleSwitch
+                + LINE_SEP + "tag: " + (if (mTagIsSpace) "null" else mGlobalTag)
+                + LINE_SEP + "head: " + mLogHeadSwitch
+                + LINE_SEP + "file: " + mLog2FileSwitch
+                + LINE_SEP + "dir: " + (if (mDir == null) mDefaultDir else mDir)
+                + LINE_SEP + "filePrefix: " + mFilePrefix
+                + LINE_SEP + "border: " + mLogBorderSwitch
+                + LINE_SEP + "singleTag: " + mSingleTagSwitch
+                + LINE_SEP + "consoleFilter: " + T[mConsoleFilter - V]
+                + LINE_SEP + "fileFilter: " + T[mFileFilter - V]
+                + LINE_SEP + "stackDeep: " + mStackDeep
+                + LINE_SEP + "stackOffset: " + mStackOffset
+                + LINE_SEP + "saveDays: " + mSaveDays
+                + LINE_SEP + "formatter: " + I_FORMATTER_MAP)
+    }
+}
+
+abstract class IFormatter {
+    abstract fun format(t: T): String
+}
+
+private class TagHead internal constructor(internal var tag: String,
+                                           internal var consoleHead: Array,
+                                           internal var fileHead: String)
+
+private object LogFormatter {
+    internal fun formatJson(json: String): String {
+        var json = json
+        try {
+            if (json.startsWith("{")) {
+                json = JSONObject(json).toString(4)
+            } else if (json.startsWith("[")) {
+                json = JSONArray(json).toString(4)
+            }
+        } catch (e: JSONException) {
+            e.printStackTrace()
+        }
+
+        return json
+    }
+
+    internal fun formatXml(xml: String): String {
+        var xml = xml
+        try {
+            val xmlInput = StreamSource(StringReader(xml))
+            val xmlOutput = StreamResult(StringWriter())
+            val transformer = TransformerFactory.newInstance().newTransformer()
+            transformer.setOutputProperty(OutputKeys.INDENT, "yes")
+            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4")
+            transformer.transform(xmlInput, xmlOutput)
+            xml = xmlOutput.getWriter().toString().replaceFirst(">", ">$LINE_SEP")
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+
+        return xml
+    }
+
+    internal fun array2String(obj: Any): String {
+        if (obj is Array<*>) {
+            return Arrays.deepToString(obj)
+        } else if (obj is BooleanArray) {
+            return Arrays.toString(obj)
+        } else if (obj is ByteArray) {
+            return Arrays.toString(obj)
+        } else if (obj is CharArray) {
+            return Arrays.toString(obj)
+        } else if (obj is DoubleArray) {
+            return Arrays.toString(obj)
+        } else if (obj is FloatArray) {
+            return Arrays.toString(obj)
+        } else if (obj is IntArray) {
+            return Arrays.toString(obj)
+        } else if (obj is LongArray) {
+            return Arrays.toString(obj)
+        } else if (obj is ShortArray) {
+            return Arrays.toString(obj)
+        }
+        throw IllegalArgumentException("Array has incompatible type: " + obj.javaClass)
+    }
+
+    internal fun throwable2String(e: Throwable): String {
+        var t: Throwable? = e
+        while (t != null) {
+            if (t is UnknownHostException) {
+                return ""
+            }
+            t = t.cause
+        }
+        val sw = StringWriter()
+        val pw = PrintWriter(sw)
+        e.printStackTrace(pw)
+        var cause: Throwable? = e.cause
+        while (cause != null) {
+            cause.printStackTrace(pw)
+            cause = cause.cause
+        }
+        pw.flush()
+        return sw.toString()
+    }
+
+    internal fun bundle2String(bundle: Bundle): String {
+        val iterator = bundle.keySet().iterator()
+        if (!iterator.hasNext()) {
+            return "Bundle {}"
+        }
+        val sb = StringBuilder(128)
+        sb.append("Bundle { ")
+        while (true) {
+            val key = iterator.next()
+            val value = bundle.get(key)
+            sb.append(key).append('=')
+            if (value != null && value is Bundle) {
+                sb.append(if (value === bundle) "(this Bundle)" else bundle2String(value))
+            } else {
+                sb.append(formatObject(value))
+            }
+            if (!iterator.hasNext()) return sb.append(" }").toString()
+            sb.append(',').append(' ')
+        }
+    }
+
+    internal fun intent2String(intent: Intent): String {
+        val sb = StringBuilder(128)
+        sb.append("Intent { ")
+        var first = true
+        val mAction = intent.action
+        if (mAction != null) {
+            sb.append("act=").append(mAction)
+            first = false
+        }
+        val mCategories = intent.categories
+        if (mCategories != null) {
+            if (!first) {
+                sb.append(' ')
+            }
+            first = false
+            sb.append("cat=[")
+            var firstCategory = true
+            for (c in mCategories) {
+                if (!firstCategory) {
+                    sb.append(',')
+                }
+                sb.append(c)
+                firstCategory = false
+            }
+            sb.append("]")
+        }
+        val mData = intent.data
+        if (mData != null) {
+            if (!first) {
+                sb.append(' ')
+            }
+            first = false
+            sb.append("dat=").append(mData)
+        }
+        val mType = intent.type
+        if (mType != null) {
+            if (!first) {
+                sb.append(' ')
+            }
+            first = false
+            sb.append("typ=").append(mType)
+        }
+        val mFlags = intent.flags
+        if (mFlags != 0) {
+            if (!first) {
+                sb.append(' ')
+            }
+            first = false
+            sb.append("flg=0x").append(Integer.toHexString(mFlags))
+        }
+        val mPackage = intent.getPackage()
+        if (mPackage != null) {
+            if (!first) {
+                sb.append(' ')
+            }
+            first = false
+            sb.append("pkg=").append(mPackage)
+        }
+        val mComponent = intent.component
+        if (mComponent != null) {
+            if (!first) {
+                sb.append(' ')
+            }
+            first = false
+            sb.append("cmp=").append(mComponent.flattenToShortString())
+        }
+        val mSourceBounds = intent.sourceBounds
+        if (mSourceBounds != null) {
+            if (!first) {
+                sb.append(' ')
+            }
+            first = false
+            sb.append("bnds=").append(mSourceBounds.toShortString())
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            val mClipData = intent.clipData
+            if (mClipData != null) {
+                if (!first) {
+                    sb.append(' ')
+                }
+                first = false
+                clipData2String(mClipData, sb)
+            }
+        }
+        val mExtras = intent.extras
+        if (mExtras != null) {
+            if (!first) {
+                sb.append(' ')
+            }
+            first = false
+            sb.append("extras={")
+            sb.append(bundle2String(mExtras))
+            sb.append('}')
+        }
+        if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+            val mSelector = intent.selector
+            if (mSelector != null) {
+                if (!first) {
+                    sb.append(' ')
+                }
+                first = false
+                sb.append("sel={")
+                sb.append(if (mSelector === intent) "(this Intent)" else intent2String(mSelector))
+                sb.append("}")
+            }
+        }
+        sb.append(" }")
+        return sb.toString()
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    private fun clipData2String(clipData: ClipData, sb: StringBuilder) {
+        val item = clipData.getItemAt(0)
+        if (item == null) {
+            sb.append("ClipData.Item {}")
+            return
+        }
+        sb.append("ClipData.Item { ")
+        val mHtmlText = item.htmlText
+        if (mHtmlText != null) {
+            sb.append("H:")
+            sb.append(mHtmlText)
+            sb.append("}")
+            return
+        }
+        val mText = item.text
+        if (mText != null) {
+            sb.append("T:")
+            sb.append(mText)
+            sb.append("}")
+            return
+        }
+        val uri = item.uri
+        if (uri != null) {
+            sb.append("U:").append(uri)
+            sb.append("}")
+            return
+        }
+        val intent = item.intent
+        if (intent != null) {
+            sb.append("I:")
+            sb.append(intent2String(intent))
+            sb.append("}")
+            return
+        }
+        sb.append("NULL")
+        sb.append("}")
+    }
+}
+
+fun  getTypeClassFromParadigm(formatter: IFormatter): Class<*>? {
+    val genericInterfaces = formatter.javaClass.genericInterfaces
+    var type: Type
+    if (genericInterfaces.size == 1) {
+        type = genericInterfaces[0]
+    } else {
+        type = formatter.javaClass.genericSuperclass
+    }
+    type = (type as ParameterizedType).getActualTypeArguments()[0]
+    while (type is ParameterizedType) {
+        type = (type as ParameterizedType).getRawType()
+    }
+    var className = type.toString()
+    if (className.startsWith("class ")) {
+        className = className.substring(6)
+    } else if (className.startsWith("interface ")) {
+        className = className.substring(10)
+    }
+    try {
+        return Class.forName(className)
+    } catch (e: ClassNotFoundException) {
+        e.printStackTrace()
+    }
+
+    return null
+}
+
+private fun getClassFromObject(obj: Any): Class<*> {
+    val objClass = obj.javaClass
+    if (objClass.isAnonymousClass || objClass.isSynthetic) {
+        val genericInterfaces = objClass.genericInterfaces
+        var className: String
+        if (genericInterfaces.size == 1) {// interface
+            var type = genericInterfaces[0]
+            while (type is ParameterizedType) {
+                type = (type as ParameterizedType).getRawType()
+            }
+            className = type.toString()
+        } else {// abstract class or lambda
+            var type = objClass.genericSuperclass
+            while (type is ParameterizedType) {
+                type = (type as ParameterizedType).getRawType()
+            }
+            className = type.toString()
+        }
+
+        if (className.startsWith("class ")) {
+            className = className.substring(6)
+        } else if (className.startsWith("interface ")) {
+            className = className.substring(10)
+        }
+        try {
+            return Class.forName(className)
+        } catch (e: ClassNotFoundException) {
+            e.printStackTrace()
+        }
+
+    }
+    return objClass
+}
\ No newline at end of file
diff --git a/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/StringUtils.kt b/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/StringUtils.kt
index 8aaaaf27a361ab98d8436f9bba884d5ff764fba2..424cc89232b74f1c6decbb2210f4c6beb8271463 100644
--- a/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/StringUtils.kt
+++ b/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/StringUtils.kt
@@ -1,185 +1,197 @@
-@file:JvmName("StringUtils")
-
 package com.blankj.utilcode.util
 
-/**
- * Return whether the string is null or 0-length.
- *
- * @param s The string.
- * @return `true`: yes

`false`: no - */ -fun isEmpty(s: CharSequence?): Boolean { - return s == null || s.isEmpty() -} +object StringUtils { + /** + * Return whether the string is null or 0-length. + * + * @param s The string. + * @return `true`: yes

`false`: no + */ + @JvmStatic + fun isEmpty(s: CharSequence?): Boolean { + return s == null || s.isEmpty() + } -/** - * Return whether the string is null or whitespace. - * - * @param s The string. - * @return `true`: yes

`false`: no - */ -fun isTrimEmpty(s: String?): Boolean { - return s == null || s.trim { it <= ' ' }.isEmpty() -} + /** + * Return whether the string is null or whitespace. + * + * @param s The string. + * @return `true`: yes

`false`: no + */ + @JvmStatic + fun isTrimEmpty(s: String?): Boolean { + return s == null || s.trim { it <= ' ' }.isEmpty() + } -/** - * Return whether the string is null or white space. - * - * @param s The string. - * @return `true`: yes

`false`: no - */ -fun isSpace(s: String?): Boolean { - if (s == null) return true - var i = 0 - val len = s.length - while (i < len) { - if (!Character.isWhitespace(s[i])) { - return false + /** + * Return whether the string is null or white space. + * + * @param s The string. + * @return `true`: yes

`false`: no + */ + @JvmStatic + fun isSpace(s: String?): Boolean { + if (s == null) return true + var i = 0 + val len = s.length + while (i < len) { + if (!Character.isWhitespace(s[i])) { + return false + } + ++i } - ++i + return true } - return true -} -/** - * Return whether string1 is equals to string2. - * - * @param s1 The first string. - * @param s2 The second string. - * @return `true`: yes

`false`: no - */ -fun equals(s1: CharSequence?, s2: CharSequence?): Boolean { - if (s1 === s2) return true - if (s1 != null && s2 != null) { - val length: Int = s1.length - if (length == s2.length) { - if (s1 is String && s2 is String) { - return s1 == s2 - } else { - for (i in 0 until length) { - if (s1[i] != s2[i]) return false + /** + * Return whether string1 is equals to string2. + * + * @param s1 The first string. + * @param s2 The second string. + * @return `true`: yes

`false`: no + */ + @JvmStatic + fun equals(s1: CharSequence?, s2: CharSequence?): Boolean { + if (s1 === s2) return true + if (s1 != null && s2 != null) { + val length: Int = s1.length + if (length == s2.length) { + if (s1 is String && s2 is String) { + return s1 == s2 + } else { + for (i in 0 until length) { + if (s1[i] != s2[i]) return false + } + return true } - return true } } + return false } - return false -} -/** - * Return whether string1 is equals to string2, ignoring case considerations.. - * - * @param s1 The first string. - * @param s2 The second string. - * @return `true`: yes

`false`: no - */ -fun equalsIgnoreCase(s1: String?, s2: String?): Boolean { - return s1.equals(s2, ignoreCase = true) -} + /** + * Return whether string1 is equals to string2, ignoring case considerations.. + * + * @param s1 The first string. + * @param s2 The second string. + * @return `true`: yes

`false`: no + */ + @JvmStatic + fun equalsIgnoreCase(s1: String?, s2: String?): Boolean { + return s1.equals(s2, ignoreCase = true) + } -/** - * Return `""` if string equals null. - * - * @param s The string. - * @return `""` if string equals null - */ -fun null2Length0(s: String?): String { - return s ?: "" -} + /** + * Return `""` if string equals null. + * + * @param s The string. + * @return `""` if string equals null + */ + @JvmStatic + fun null2Length0(s: String?): String { + return s ?: "" + } -/** - * Return the length of string. - * - * @param s The string. - * @return the length of string - */ -fun length(s: CharSequence?): Int { - return s?.length ?: 0 -} + /** + * Return the length of string. + * + * @param s The string. + * @return the length of string + */ + @JvmStatic + fun length(s: CharSequence?): Int { + return s?.length ?: 0 + } -/** - * Set the first letter of string upper. - * - * @param s The string. - * @return the string with first letter upper. - */ -fun upperFirstLetter(s: String?): String { - if (s == null || s.isEmpty()) return "" - return if (!Character.isLowerCase(s[0])) s else (s[0].toInt() - 32).toChar().toString() + s.substring(1) -} + /** + * Set the first letter of string upper. + * + * @param s The string. + * @return the string with first letter upper. + */ + @JvmStatic + fun upperFirstLetter(s: String?): String { + if (s == null || s.isEmpty()) return "" + return if (!Character.isLowerCase(s[0])) s else (s[0].toInt() - 32).toChar().toString() + s.substring(1) + } -/** - * Set the first letter of string lower. - * - * @param s The string. - * @return the string with first letter lower. - */ -fun lowerFirstLetter(s: String?): String { - if (s == null || s.isEmpty()) return "" - return if (!Character.isUpperCase(s[0])) s else (s[0].toInt() + 32).toChar().toString() + s.substring(1) -} + /** + * Set the first letter of string lower. + * + * @param s The string. + * @return the string with first letter lower. + */ + @JvmStatic + fun lowerFirstLetter(s: String?): String { + if (s == null || s.isEmpty()) return "" + return if (!Character.isUpperCase(s[0])) s else (s[0].toInt() + 32).toChar().toString() + s.substring(1) + } -/** - * Reverse the string. - * - * @param s The string. - * @return the reverse string. - */ -fun reverse(s: String?): String { - if (s == null) return "" - val len = s.length - if (len <= 1) return s - val mid = len shr 1 - val chars = s.toCharArray() - var c: Char - for (i in 0 until mid) { - c = chars[i] - chars[i] = chars[len - i - 1] - chars[len - i - 1] = c + /** + * Reverse the string. + * + * @param s The string. + * @return the reverse string. + */ + @JvmStatic + fun reverse(s: String?): String { + if (s == null) return "" + val len = s.length + if (len <= 1) return s + val mid = len shr 1 + val chars = s.toCharArray() + var c: Char + for (i in 0 until mid) { + c = chars[i] + chars[i] = chars[len - i - 1] + chars[len - i - 1] = c + } + return String(chars) } - return String(chars) -} -/** - * Convert string to DBC. - * - * @param s The string. - * @return the DBC string - */ -fun toDBC(s: String?): String { - if (s == null || s.isEmpty()) return "" - val chars = s.toCharArray() - var i = 0 - val len = chars.size - while (i < len) { - when { - chars[i].toInt() == 12288 -> chars[i] = ' ' - chars[i].toInt() in 65281..65374 -> chars[i] = (chars[i].toInt() - 65248).toChar() - else -> chars[i] = chars[i] + /** + * Convert string to DBC. + * + * @param s The string. + * @return the DBC string + */ + @JvmStatic + fun toDBC(s: String?): String { + if (s == null || s.isEmpty()) return "" + val chars = s.toCharArray() + var i = 0 + val len = chars.size + while (i < len) { + when { + chars[i].toInt() == 12288 -> chars[i] = ' ' + chars[i].toInt() in 65281..65374 -> chars[i] = (chars[i].toInt() - 65248).toChar() + else -> chars[i] = chars[i] + } + i++ } - i++ + return String(chars) } - return String(chars) -} -/** - * Convert string to SBC. - * - * @param s The string. - * @return the SBC string - */ -fun toSBC(s: String?): String { - if (s == null || s.isEmpty()) return "" - val chars = s.toCharArray() - var i = 0 - val len = chars.size - while (i < len) { - when { - chars[i] == ' ' -> chars[i] = 12288.toChar() - chars[i].toInt() in 33..126 -> chars[i] = (chars[i].toInt() + 65248).toChar() - else -> chars[i] = chars[i] + /** + * Convert string to SBC. + * + * @param s The string. + * @return the SBC string + */ + @JvmStatic + fun toSBC(s: String?): String { + if (s == null || s.isEmpty()) return "" + val chars = s.toCharArray() + var i = 0 + val len = chars.size + while (i < len) { + when { + chars[i] == ' ' -> chars[i] = 12288.toChar() + chars[i].toInt() in 33..126 -> chars[i] = (chars[i].toInt() + 65248).toChar() + else -> chars[i] = chars[i] + } + i++ } - i++ + return String(chars) } - return String(chars) } \ No newline at end of file diff --git a/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/Utils.java b/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/Utils.java deleted file mode 100644 index b30e106908207af27334a8d6152bb6f2f748b8dc..0000000000000000000000000000000000000000 --- a/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/Utils.java +++ /dev/null @@ -1,358 +0,0 @@ -package com.blankj.utilcode.util; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.ActivityManager; -import android.app.Application; -import android.app.Application.ActivityLifecycleCallbacks; -import android.content.Context; -import android.content.res.Resources; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.content.FileProvider; -import android.util.DisplayMetrics; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -/** - *
- *     author:
- *                                      ___           ___           ___         ___
- *         _____                       /  /\         /__/\         /__/|       /  /\
- *        /  /::\                     /  /::\        \  \:\       |  |:|      /  /:/
- *       /  /:/\:\    ___     ___    /  /:/\:\        \  \:\      |  |:|     /__/::\
- *      /  /:/~/::\  /__/\   /  /\  /  /:/~/::\   _____\__\:\   __|  |:|     \__\/\:\
- *     /__/:/ /:/\:| \  \:\ /  /:/ /__/:/ /:/\:\ /__/::::::::\ /__/\_|:|____    \  \:\
- *     \  \:\/:/~/:/  \  \:\  /:/  \  \:\/:/__\/ \  \:\~~\~~\/ \  \:\/:::::/     \__\:\
- *      \  \::/ /:/    \  \:\/:/    \  \::/       \  \:\  ~~~   \  \::/~~~~      /  /:/
- *       \  \:\/:/      \  \::/      \  \:\        \  \:\        \  \:\         /__/:/
- *        \  \::/        \__\/        \  \:\        \  \:\        \  \:\        \__\/
- *         \__\/                       \__\/         \__\/         \__\/
- *     blog  : http://blankj.com
- *     time  : 16/12/08
- *     desc  : utils about initialization
- * 
- */ -public final class Utils { - - @SuppressLint("StaticFieldLeak") - private static Application sApplication; - - private static final ActivityLifecycleImpl ACTIVITY_LIFECYCLE = new ActivityLifecycleImpl(); - - private Utils() { - throw new UnsupportedOperationException("u can't instantiate me..."); - } - - /** - * Init utils. - *

Init it in the class of Application.

- * - * @param context context - */ - public static void init(final Context context) { - if (context == null) { - init(getApplicationByReflect()); - return; - } - init((Application) context.getApplicationContext()); - } - - /** - * Init utils. - *

Init it in the class of Application.

- * - * @param app application - */ - public static void init(final Application app) { - if (sApplication == null) { - if (app == null) { - sApplication = getApplicationByReflect(); - } else { - sApplication = app; - } - sApplication.registerActivityLifecycleCallbacks(ACTIVITY_LIFECYCLE); - } - } - - /** - * Return the context of Application object. - * - * @return the context of Application object - */ - public static Application getApp() { - if (sApplication != null) return sApplication; - Application app = getApplicationByReflect(); - init(app); - return app; - } - - private static Application getApplicationByReflect() { - try { - @SuppressLint("PrivateApi") - Class activityThread = Class.forName("android.app.ActivityThread"); - Object thread = activityThread.getMethod("currentActivityThread").invoke(null); - Object app = activityThread.getMethod("getApplication").invoke(thread); - if (app == null) { - throw new NullPointerException("u should init first"); - } - return (Application) app; - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } - throw new NullPointerException("u should init first"); - } - - static ActivityLifecycleImpl getActivityLifecycle() { - return ACTIVITY_LIFECYCLE; - } - - static LinkedList getActivityList() { - return ACTIVITY_LIFECYCLE.mActivityList; - } - - static Context getTopActivityOrApp() { - if (isAppForeground()) { - Activity topActivity = ACTIVITY_LIFECYCLE.getTopActivity(); - return topActivity == null ? Utils.getApp() : topActivity; - } else { - return Utils.getApp(); - } - } - - static boolean isAppForeground() { - ActivityManager am = - (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE); - //noinspection ConstantConditions - List info = am.getRunningAppProcesses(); - if (info == null || info.size() == 0) return false; - for (ActivityManager.RunningAppProcessInfo aInfo : info) { - if (aInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { - return aInfo.processName.equals(Utils.getApp().getPackageName()); - } - } - return false; - } - - static final AdaptScreenArgs ADAPT_SCREEN_ARGS = new AdaptScreenArgs(); - - static void restoreAdaptScreen() { - final DisplayMetrics systemDm = Resources.getSystem().getDisplayMetrics(); - final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics(); - final Activity activity = ACTIVITY_LIFECYCLE.getTopActivity(); - if (activity != null) { - final DisplayMetrics activityDm = activity.getResources().getDisplayMetrics(); - if (ADAPT_SCREEN_ARGS.isVerticalSlide) { - activityDm.density = activityDm.widthPixels / (float) ADAPT_SCREEN_ARGS.sizeInPx; - } else { - activityDm.density = activityDm.heightPixels / (float) ADAPT_SCREEN_ARGS.sizeInPx; - } - activityDm.scaledDensity = activityDm.density * (systemDm.scaledDensity / systemDm.density); - activityDm.densityDpi = (int) (160 * activityDm.density); - - appDm.density = activityDm.density; - appDm.scaledDensity = activityDm.scaledDensity; - appDm.densityDpi = activityDm.densityDpi; - } else { - if (ADAPT_SCREEN_ARGS.isVerticalSlide) { - appDm.density = appDm.widthPixels / (float) ADAPT_SCREEN_ARGS.sizeInPx; - } else { - appDm.density = appDm.heightPixels / (float) ADAPT_SCREEN_ARGS.sizeInPx; - } - appDm.scaledDensity = appDm.density * (systemDm.scaledDensity / systemDm.density); - appDm.densityDpi = (int) (160 * appDm.density); - } - } - - static void cancelAdaptScreen() { - final DisplayMetrics systemDm = Resources.getSystem().getDisplayMetrics(); - final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics(); - final Activity activity = ACTIVITY_LIFECYCLE.getTopActivity(); - if (activity != null) { - final DisplayMetrics activityDm = activity.getResources().getDisplayMetrics(); - activityDm.density = systemDm.density; - activityDm.scaledDensity = systemDm.scaledDensity; - activityDm.densityDpi = systemDm.densityDpi; - } - appDm.density = systemDm.density; - appDm.scaledDensity = systemDm.scaledDensity; - appDm.densityDpi = systemDm.densityDpi; - } - - static boolean isAdaptScreen() { - final DisplayMetrics systemDm = Resources.getSystem().getDisplayMetrics(); - final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics(); - return systemDm.density != appDm.density; - } - - static class AdaptScreenArgs { - int sizeInPx; - boolean isVerticalSlide; - } - - static class ActivityLifecycleImpl implements ActivityLifecycleCallbacks { - - final LinkedList mActivityList = new LinkedList<>(); - final HashMap mStatusListenerMap = new HashMap<>(); - - private int mForegroundCount = 0; - private int mConfigCount = 0; - - void addListener(final Object object, final OnAppStatusChangedListener listener) { - mStatusListenerMap.put(object, listener); - } - - void removeListener(final Object object) { - mStatusListenerMap.remove(object); - } - - @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - setTopActivity(activity); - } - - @Override - public void onActivityStarted(Activity activity) { - setTopActivity(activity); - if (mForegroundCount <= 0) { - postStatus(true); - } - if (mConfigCount < 0) { - ++mConfigCount; - } else { - ++mForegroundCount; - } - } - - @Override - public void onActivityResumed(Activity activity) { - setTopActivity(activity); - } - - @Override - public void onActivityPaused(Activity activity) {/**/} - - @Override - public void onActivityStopped(Activity activity) { - if (activity.isChangingConfigurations()) { - --mConfigCount; - } else { - --mForegroundCount; - if (mForegroundCount <= 0) { - postStatus(false); - } - } - } - - @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) {/**/} - - @Override - public void onActivityDestroyed(Activity activity) { - mActivityList.remove(activity); - } - - private void postStatus(final boolean isForeground) { - if (mStatusListenerMap.isEmpty()) return; - for (OnAppStatusChangedListener onAppStatusChangedListener : mStatusListenerMap.values()) { - if (onAppStatusChangedListener == null) return; - if (isForeground) { - onAppStatusChangedListener.onForeground(); - } else { - onAppStatusChangedListener.onBackground(); - } - } - } - - private void setTopActivity(final Activity activity) { - if (activity.getClass() == PermissionUtils.PermissionActivity.class) return; - if (mActivityList.contains(activity)) { - if (!mActivityList.getLast().equals(activity)) { - mActivityList.remove(activity); - mActivityList.addLast(activity); - } - } else { - mActivityList.addLast(activity); - } - } - - Activity getTopActivity() { - if (!mActivityList.isEmpty()) { - final Activity topActivity = mActivityList.getLast(); - if (topActivity != null) { - return topActivity; - } - } - Activity topActivityByReflect = getTopActivityByReflect(); - if (topActivityByReflect != null) { - setTopActivity(topActivityByReflect); - } - return topActivityByReflect; - } - - private Activity getTopActivityByReflect() { - try { - @SuppressLint("PrivateApi") - Class activityThreadClass = Class.forName("android.app.ActivityThread"); - Object activityThread = activityThreadClass.getMethod("currentActivityThread").invoke(null); - Field activitiesField = activityThreadClass.getDeclaredField("mActivityList"); - activitiesField.setAccessible(true); - Map activities = (Map) activitiesField.get(activityThread); - if (activities == null) return null; - for (Object activityRecord : activities.values()) { - Class activityRecordClass = activityRecord.getClass(); - Field pausedField = activityRecordClass.getDeclaredField("paused"); - pausedField.setAccessible(true); - if (!pausedField.getBoolean(activityRecord)) { - Field activityField = activityRecordClass.getDeclaredField("activity"); - activityField.setAccessible(true); - return (Activity) activityField.get(activityRecord); - } - } - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (NoSuchFieldException e) { - e.printStackTrace(); - } - return null; - } - } - - - public static final class FileProvider4UtilCode extends FileProvider { - - @Override - public boolean onCreate() { - Utils.init(getContext()); - return true; - } - } - - /////////////////////////////////////////////////////////////////////////// - // interface - /////////////////////////////////////////////////////////////////////////// - - public interface OnAppStatusChangedListener { - void onForeground(); - - void onBackground(); - } -} diff --git a/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/Utils.kt b/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/Utils.kt new file mode 100644 index 0000000000000000000000000000000000000000..35e3fad7e107a9699a19e3a95132dd8f87824a19 --- /dev/null +++ b/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/Utils.kt @@ -0,0 +1,317 @@ +@file:JvmName("Utils") + +package com.blankj.utilcode.util + +import android.annotation.SuppressLint +import android.app.Activity +import android.app.ActivityManager +import android.app.Application +import android.app.Application.ActivityLifecycleCallbacks +import android.content.Context +import android.content.res.Resources +import android.os.Bundle +import android.support.v4.content.FileProvider +import java.lang.reflect.InvocationTargetException +import java.util.* + +@SuppressLint("StaticFieldLeak") +private var sApplication: Application? = null + +private val ACTIVITY_LIFECYCLE = ActivityLifecycleImpl() + +/** + * Init utils. + * + * Init it in the class of Application. + * + * @param context context + */ +fun init(context: Context?) { + if (context == null) { + init(getApplicationByReflect()) + return + } + init(context.applicationContext as Application) +} + +/** + * Init utils. + * + * Init it in the class of Application. + * + * @param app application + */ +fun init(app: Application?) { + if (sApplication == null) { + sApplication = app ?: getApplicationByReflect() + sApplication!!.registerActivityLifecycleCallbacks(ACTIVITY_LIFECYCLE) + } +} + +/** + * Return the context of Application object. + * + * @return the context of Application object + */ +fun getApp(): Application { + if (sApplication != null) return sApplication as Application + val app = getApplicationByReflect() + init(app) + return app +} + +private fun getApplicationByReflect(): Application { + try { + @SuppressLint("PrivateApi") + val activityThread = Class.forName("android.app.ActivityThread") + val thread = activityThread.getMethod("currentActivityThread").invoke(null) + val app = activityThread.getMethod("getApplication").invoke(thread) + ?: throw NullPointerException("u should init first") + return app as Application + } catch (e: NoSuchMethodException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } catch (e: InvocationTargetException) { + e.printStackTrace() + } catch (e: ClassNotFoundException) { + e.printStackTrace() + } + + throw NullPointerException("u should init first") +} + +internal fun getActivityLifecycle(): ActivityLifecycleImpl { + return ACTIVITY_LIFECYCLE +} + +internal fun getActivityList(): LinkedList { + return ACTIVITY_LIFECYCLE.mActivityList +} + +fun getTopActivityOrApp(): Context { + return if (isAppForeground()) { + val topActivity = ACTIVITY_LIFECYCLE.topActivity + topActivity ?: getApp() + } else { + getApp() + } +} + +internal fun isAppForeground(): Boolean { + val am = getApp().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + + val info = am.runningAppProcesses + if (info == null || info.size == 0) return false + for (aInfo in info) { + if (aInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { + return aInfo.processName == getApp().packageName + } + } + return false +} + +internal val ADAPT_SCREEN_ARGS = AdaptScreenArgs() + +internal fun restoreAdaptScreen() { + val systemDm = Resources.getSystem().displayMetrics + val appDm = getApp().resources.displayMetrics + val activity = ACTIVITY_LIFECYCLE.topActivity + if (activity != null) { + val activityDm = activity.resources.displayMetrics + if (ADAPT_SCREEN_ARGS.isVerticalSlide) { + activityDm.density = activityDm.widthPixels / ADAPT_SCREEN_ARGS.sizeInPx.toFloat() + } else { + activityDm.density = activityDm.heightPixels / ADAPT_SCREEN_ARGS.sizeInPx.toFloat() + } + activityDm.scaledDensity = activityDm.density * (systemDm.scaledDensity / systemDm.density) + activityDm.densityDpi = (160 * activityDm.density).toInt() + + appDm.density = activityDm.density + appDm.scaledDensity = activityDm.scaledDensity + appDm.densityDpi = activityDm.densityDpi + } else { + if (ADAPT_SCREEN_ARGS.isVerticalSlide) { + appDm.density = appDm.widthPixels / ADAPT_SCREEN_ARGS.sizeInPx.toFloat() + } else { + appDm.density = appDm.heightPixels / ADAPT_SCREEN_ARGS.sizeInPx.toFloat() + } + appDm.scaledDensity = appDm.density * (systemDm.scaledDensity / systemDm.density) + appDm.densityDpi = (160 * appDm.density).toInt() + } +} + +internal fun cancelAdaptScreen() { + val systemDm = Resources.getSystem().displayMetrics + val appDm = getApp().resources.displayMetrics + val activity = ACTIVITY_LIFECYCLE.topActivity + if (activity != null) { + val activityDm = activity.resources.displayMetrics + activityDm.density = systemDm.density + activityDm.scaledDensity = systemDm.scaledDensity + activityDm.densityDpi = systemDm.densityDpi + } + appDm.density = systemDm.density + appDm.scaledDensity = systemDm.scaledDensity + appDm.densityDpi = systemDm.densityDpi +} + +internal fun isAdaptScreen(): Boolean { + val systemDm = Resources.getSystem().displayMetrics + val appDm = getApp().resources.displayMetrics + return systemDm.density != appDm.density +} + +internal class AdaptScreenArgs { + var sizeInPx: Int = 0 + var isVerticalSlide: Boolean = false +} + +internal class ActivityLifecycleImpl : ActivityLifecycleCallbacks { + + val mActivityList: LinkedList = LinkedList() + private val mStatusListenerMap: HashMap = HashMap() + + private var mForegroundCount = 0 + private var mConfigCount = 0 + + var topActivity: Activity? + get() { + if (!mActivityList.isEmpty()) { + val topActivity = mActivityList.last + if (topActivity != null) { + return topActivity + } + } + val topActivityByReflect = topActivityByReflect + if (topActivityByReflect != null) { + topActivity = topActivityByReflect + } + return topActivityByReflect + } + private set(activity) { + if (activity?.javaClass == PermissionUtils.PermissionActivity::class.java) return + if (mActivityList.contains(activity)) { + if (!mActivityList.last.equals(activity)) { + mActivityList.remove(activity) + mActivityList.addLast(activity) + } + } else { + mActivityList.addLast(activity) + } + } + + private val topActivityByReflect: Activity? + get() { + try { + @SuppressLint("PrivateApi") + val activityThreadClass = Class.forName("android.app.ActivityThread") + val activityThread = activityThreadClass.getMethod("currentActivityThread").invoke(null) + val activitiesField = activityThreadClass.getDeclaredField("mActivityList") + activitiesField.isAccessible = true + val activities = activitiesField.get(activityThread) as Map<*, *> + for (activityRecord in activities.values) { + if (activityRecord == null) continue + val activityRecordClass = activityRecord.javaClass + val pausedField = activityRecordClass.getDeclaredField("paused") + pausedField.isAccessible = true + if (!pausedField.getBoolean(activityRecord)) { + val activityField = activityRecordClass.getDeclaredField("activity") + activityField.isAccessible = true + return activityField.get(activityRecord) as Activity + } + } + } catch (e: ClassNotFoundException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } catch (e: InvocationTargetException) { + e.printStackTrace() + } catch (e: NoSuchMethodException) { + e.printStackTrace() + } catch (e: NoSuchFieldException) { + e.printStackTrace() + } + + return null + } + + fun addListener(obj: Any, listener: OnAppStatusChangedListener) { + mStatusListenerMap[obj] = listener + } + + fun removeListener(obj: Any) { + mStatusListenerMap.remove(obj) + } + + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle) { + topActivity = activity + } + + override fun onActivityStarted(activity: Activity) { + topActivity = activity + if (mForegroundCount <= 0) { + postStatus(true) + } + if (mConfigCount < 0) { + ++mConfigCount + } else { + ++mForegroundCount + } + } + + override fun onActivityResumed(activity: Activity) { + topActivity = activity + } + + override fun onActivityPaused(activity: Activity) {/**/ + } + + override fun onActivityStopped(activity: Activity) { + if (activity.isChangingConfigurations) { + --mConfigCount + } else { + --mForegroundCount + if (mForegroundCount <= 0) { + postStatus(false) + } + } + } + + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {/**/ + } + + override fun onActivityDestroyed(activity: Activity) { + mActivityList.remove(activity) + } + + private fun postStatus(isForeground: Boolean) { + if (mStatusListenerMap.isEmpty()) return + for (onAppStatusChangedListener in mStatusListenerMap.values) { + if (isForeground) { + onAppStatusChangedListener.onForeground() + } else { + onAppStatusChangedListener.onBackground() + } + } + } +} + + +class FileProvider4UtilCode : FileProvider() { + + override fun onCreate(): Boolean { + init(context) + return true + } +} + +/////////////////////////////////////////////////////////////////////////// +// interface +/////////////////////////////////////////////////////////////////////////// + +interface OnAppStatusChangedListener { + fun onForeground() + + fun onBackground() +} \ No newline at end of file diff --git a/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/ZipUtils.java b/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/ZipUtils.java index 1cb27527a2b35fb1675511beef72326e0a1f5a5c..0bfe715353c6b970e0a8b1dc553a44c7f0f6aa99 100644 --- a/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/ZipUtils.java +++ b/utilcode-kotlin/src/main/java/com/blankj/utilcode/util/ZipUtils.java @@ -1,5 +1,7 @@ package com.blankj.utilcode.util; +import android.util.Log; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; @@ -291,12 +293,22 @@ public final class ZipUtils { if (isSpace(keyword)) { while (entries.hasMoreElements()) { ZipEntry entry = ((ZipEntry) entries.nextElement()); + String entryName = entry.getName(); + if (entryName.contains("../")) { + Log.e("ZipUtils", "it's dangerous!"); + return files; + } if (!unzipChildFile(destDir, files, zip, entry)) return files; } } else { while (entries.hasMoreElements()) { ZipEntry entry = ((ZipEntry) entries.nextElement()); - if (entry.getName().contains(keyword)) { + String entryName = entry.getName(); + if (entryName.contains("../")) { + Log.e("ZipUtils", "it's dangerous!"); + return files; + } + if (entryName.contains(keyword)) { if (!unzipChildFile(destDir, files, zip, entry)) return files; } } diff --git a/utilcode/src/main/java/com/blankj/utilcode/util/ZipUtils.java b/utilcode/src/main/java/com/blankj/utilcode/util/ZipUtils.java index 1cb27527a2b35fb1675511beef72326e0a1f5a5c..0bfe715353c6b970e0a8b1dc553a44c7f0f6aa99 100644 --- a/utilcode/src/main/java/com/blankj/utilcode/util/ZipUtils.java +++ b/utilcode/src/main/java/com/blankj/utilcode/util/ZipUtils.java @@ -1,5 +1,7 @@ package com.blankj.utilcode.util; +import android.util.Log; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; @@ -291,12 +293,22 @@ public final class ZipUtils { if (isSpace(keyword)) { while (entries.hasMoreElements()) { ZipEntry entry = ((ZipEntry) entries.nextElement()); + String entryName = entry.getName(); + if (entryName.contains("../")) { + Log.e("ZipUtils", "it's dangerous!"); + return files; + } if (!unzipChildFile(destDir, files, zip, entry)) return files; } } else { while (entries.hasMoreElements()) { ZipEntry entry = ((ZipEntry) entries.nextElement()); - if (entry.getName().contains(keyword)) { + String entryName = entry.getName(); + if (entryName.contains("../")) { + Log.e("ZipUtils", "it's dangerous!"); + return files; + } + if (entryName.contains(keyword)) { if (!unzipChildFile(destDir, files, zip, entry)) return files; } }