package com.cooleshow.base.utils; import android.content.ClipData; import android.content.ComponentName; import android.content.Intent; import android.graphics.Rect; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.util.Log; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Formatter; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.transform.OutputKeys; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import androidx.annotation.IntDef; import androidx.annotation.IntRange; import androidx.annotation.RequiresApi; import androidx.collection.SimpleArrayMap; /** *
 *     author: Blankj
 *     blog  : http://blankj.com
 *     time  : 2016/09/21
 *     desc  : utils about log
 * 
*/ public final class LogUtils { public static final int V = Log.VERBOSE; public static final int D = Log.DEBUG; public static final int I = Log.INFO; public static final int W = Log.WARN; public static final int E = Log.ERROR; public static final int A = Log.ASSERT; @IntDef({V, D, I, W, E, A}) @Retention(RetentionPolicy.SOURCE) public @interface TYPE { } private static final char[] T = new char[]{'V', 'D', 'I', 'W', 'E', 'A'}; private static final int FILE = 0x10; private static final int JSON = 0x20; private static final int XML = 0x30; private static final String FILE_SEP = System.getProperty("file.separator"); private static final String LINE_SEP = System.getProperty("line.separator"); private static final String TOP_CORNER = "┌"; private static final String MIDDLE_CORNER = "├"; private static final String LEFT_BORDER = "│ "; private static final String BOTTOM_CORNER = "└"; private static final String SIDE_DIVIDER = "────────────────────────────────────────────────────────"; private static final String MIDDLE_DIVIDER = "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄"; private static final String TOP_BORDER = TOP_CORNER + SIDE_DIVIDER + SIDE_DIVIDER; private static final String MIDDLE_BORDER = MIDDLE_CORNER + MIDDLE_DIVIDER + MIDDLE_DIVIDER; private static final String BOTTOM_BORDER = BOTTOM_CORNER + SIDE_DIVIDER + SIDE_DIVIDER; private static final int MAX_LEN = 1100;// fit for Chinese character private static final String NOTHING = "log nothing"; private static final String NULL = "null"; private static final String ARGS = "args"; private static final String PLACEHOLDER = " "; private static final Config CONFIG = new Config(); private static SimpleDateFormat simpleDateFormat; private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor(); private static final SimpleArrayMap I_FORMATTER_MAP = new SimpleArrayMap<>(); private LogUtils() { throw new UnsupportedOperationException("u can't instantiate me..."); } public static Config getConfig() { return CONFIG; } public static void v(final Object... contents) { log(V, CONFIG.getGlobalTag(), contents); } public static void vTag(final String tag, final Object... contents) { log(V, tag, contents); } public static void d(final Object... contents) { log(D, CONFIG.getGlobalTag(), contents); } public static void dTag(final String tag, final Object... contents) { log(D, tag, contents); } public static void i(final Object... contents) { log(I, CONFIG.getGlobalTag(), contents); } public static void iTag(final String tag, final Object... contents) { log(I, tag, contents); } public static void w(final Object... contents) { log(W, CONFIG.getGlobalTag(), contents); } public static void wTag(final String tag, final Object... contents) { log(W, tag, contents); } public static void e(final Object... contents) { log(E, CONFIG.getGlobalTag(), contents); } public static void eTag(final String tag, final Object... contents) { log(E, tag, contents); } public static void a(final Object... contents) { log(A, CONFIG.getGlobalTag(), contents); } public static void aTag(final String tag, final Object... contents) { log(A, tag, contents); } public static void file(final Object content) { log(FILE | D, CONFIG.getGlobalTag(), content); } public static void file(@TYPE final int type, final Object content) { log(FILE | type, CONFIG.getGlobalTag(), content); } public static void file(final String tag, final Object content) { log(FILE | D, tag, content); } public static void file(@TYPE final int type, final String tag, final Object content) { log(FILE | type, tag, content); } public static void json(final Object content) { log(JSON | D, CONFIG.getGlobalTag(), content); } public static void json(@TYPE final int type, final Object content) { log(JSON | type, CONFIG.getGlobalTag(), content); } public static void json(final String tag, final Object content) { log(JSON | D, tag, content); } public static void json(@TYPE final int type, final String tag, final Object content) { log(JSON | type, tag, content); } public static void xml(final String content) { log(XML | D, CONFIG.getGlobalTag(), content); } public static void xml(@TYPE final int type, final String content) { log(XML | type, CONFIG.getGlobalTag(), content); } public static void xml(final String tag, final String content) { log(XML | D, tag, content); } public static void xml(@TYPE final int type, final String tag, final String content) { log(XML | type, tag, content); } public static void log(final int type, final String tag, final Object... contents) { if (!CONFIG.isLogSwitch()) return; final int type_low = type & 0x0f, type_high = type & 0xf0; if (CONFIG.isLog2ConsoleSwitch() || CONFIG.isLog2FileSwitch() || type_high == FILE) { if (type_low < CONFIG.mConsoleFilter && type_low < CONFIG.mFileFilter) return; final TagHead tagHead = processTagAndHead(tag); final String body = processBody(type_high, contents); if (CONFIG.isLog2ConsoleSwitch() && type_high != FILE && type_low >= CONFIG.mConsoleFilter) { print2Console(type_low, tagHead.tag, tagHead.consoleHead, body); } if ((CONFIG.isLog2FileSwitch() || type_high == FILE) && type_low >= CONFIG.mFileFilter) { EXECUTOR.execute(new Runnable() { @Override public void run() { print2File(type_low, tagHead.tag, tagHead.fileHead + body); } }); } } } public static String getCurrentLogFilePath() { return getCurrentLogFilePath(new Date()); } public static List getLogFiles() { String dir = CONFIG.getDir(); File logDir = new File(dir); if (!logDir.exists()) return new ArrayList<>(); File[] files = logDir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return isMatchLogFileName(name); } }); List list = new ArrayList<>(); Collections.addAll(list, files); return list; } private static TagHead processTagAndHead(String tag) { if (!CONFIG.mTagIsSpace && !CONFIG.isLogHeadSwitch()) { tag = CONFIG.getGlobalTag(); } else { final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); final int stackIndex = 3 + CONFIG.getStackOffset(); if (stackIndex >= stackTrace.length) { StackTraceElement targetElement = stackTrace[3]; final String fileName = getFileName(targetElement); if (CONFIG.mTagIsSpace && UtilsBridge.isSpace(tag)) { int index = fileName.indexOf('.');// Use proguard may not find '.'. tag = index == -1 ? fileName : fileName.substring(0, index); } return new TagHead(tag, null, ": "); } StackTraceElement targetElement = stackTrace[stackIndex]; final String fileName = getFileName(targetElement); if (CONFIG.mTagIsSpace && UtilsBridge.isSpace(tag)) { int index = fileName.indexOf('.');// Use proguard may not find '.'. tag = index == -1 ? fileName : fileName.substring(0, index); } if (CONFIG.isLogHeadSwitch()) { String tName = Thread.currentThread().getName(); final String head = new Formatter() .format("%s, %s.%s(%s:%d)", tName, targetElement.getClassName(), targetElement.getMethodName(), fileName, targetElement.getLineNumber()) .toString(); final String fileHead = " [" + head + "]: "; if (CONFIG.getStackDeep() <= 1) { return new TagHead(tag, new String[]{head}, fileHead); } else { final String[] consoleHead = new String[Math.min( CONFIG.getStackDeep(), stackTrace.length - stackIndex )]; consoleHead[0] = head; int spaceLen = tName.length() + 2; String space = new Formatter().format("%" + spaceLen + "s", "").toString(); for (int i = 1, len = consoleHead.length; i < len; ++i) { targetElement = stackTrace[i + stackIndex]; consoleHead[i] = new Formatter() .format("%s%s.%s(%s:%d)", space, targetElement.getClassName(), targetElement.getMethodName(), getFileName(targetElement), targetElement.getLineNumber()) .toString(); } return new TagHead(tag, consoleHead, fileHead); } } } return new TagHead(tag, null, ": "); } private static String getFileName(final StackTraceElement targetElement) { String fileName = targetElement.getFileName(); if (fileName != null) return fileName; // If name of file is null, should add // "-keepattributes SourceFile,LineNumberTable" in proguard file. String className = targetElement.getClassName(); String[] classNameInfo = className.split("\\."); if (classNameInfo.length > 0) { className = classNameInfo[classNameInfo.length - 1]; } int index = className.indexOf('$'); if (index != -1) { className = className.substring(0, index); } return className + ".java"; } private static String processBody(final int type, final Object... contents) { String body = NULL; if (contents != null) { if (contents.length == 1) { body = formatObject(type, contents[0]); } else { StringBuilder sb = new StringBuilder(); for (int i = 0, len = contents.length; i < len; ++i) { Object content = contents[i]; sb.append(ARGS) .append("[") .append(i) .append("]") .append(" = ") .append(formatObject(content)) .append(LINE_SEP); } body = sb.toString(); } } return body.length() == 0 ? NOTHING : body; } private static String formatObject(int type, Object object) { if (object == null) return NULL; if (type == JSON) return LogFormatter.object2String(object, JSON); if (type == XML) return LogFormatter.object2String(object, XML); return formatObject(object); } private static String formatObject(Object object) { if (object == null) return NULL; if (!I_FORMATTER_MAP.isEmpty()) { IFormatter iFormatter = I_FORMATTER_MAP.get(getClassFromObject(object)); if (iFormatter != null) { //noinspection unchecked return iFormatter.format(object); } } return LogFormatter.object2String(object); } private static void print2Console(final int type, final String tag, final String[] head, final String msg) { if (CONFIG.isSingleTagSwitch()) { 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 static void printBorder(final int type, final String tag, boolean isTop) { if (CONFIG.isLogBorderSwitch()) { print2Console(type, tag, isTop ? TOP_BORDER : BOTTOM_BORDER); } } private static void printHead(final int type, final String tag, final String[] head) { if (head != null) { for (String aHead : head) { print2Console(type, tag, CONFIG.isLogBorderSwitch() ? LEFT_BORDER + aHead : aHead); } if (CONFIG.isLogBorderSwitch()) print2Console(type, tag, MIDDLE_BORDER); } } private static void printMsg(final int type, final String tag, final String msg) { int len = msg.length(); int countOfSub = len / MAX_LEN; if (countOfSub > 0) { int index = 0; for (int i = 0; i < countOfSub; i++) { 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 static void printSubMsg(final int type, final String tag, final String msg) { if (!CONFIG.isLogBorderSwitch()) { print2Console(type, tag, msg); return; } StringBuilder sb = new StringBuilder(); String[] lines = msg.split(LINE_SEP); for (String line : lines) { print2Console(type, tag, LEFT_BORDER + line); } } private static String processSingleTagMsg(final int type, final String tag, final String[] head, final String msg) { StringBuilder sb = new StringBuilder(); if (CONFIG.isLogBorderSwitch()) { sb.append(PLACEHOLDER).append(LINE_SEP); sb.append(TOP_BORDER).append(LINE_SEP); if (head != null) { for (String aHead : head) { sb.append(LEFT_BORDER).append(aHead).append(LINE_SEP); } sb.append(MIDDLE_BORDER).append(LINE_SEP); } for (String line : msg.split(LINE_SEP)) { sb.append(LEFT_BORDER).append(line).append(LINE_SEP); } sb.append(BOTTOM_BORDER); } else { if (head != null) { sb.append(PLACEHOLDER).append(LINE_SEP); for (String aHead : head) { sb.append(aHead).append(LINE_SEP); } } sb.append(msg); } return sb.toString(); } private static void printSingleTagMsg(final int type, final String tag, final String msg) { int len = msg.length(); int countOfSub = CONFIG.isLogBorderSwitch() ? (len - BOTTOM_BORDER.length()) / MAX_LEN : len / MAX_LEN; if (countOfSub > 0) { if (CONFIG.isLogBorderSwitch()) { print2Console(type, tag, msg.substring(0, MAX_LEN) + LINE_SEP + BOTTOM_BORDER); int index = MAX_LEN; for (int i = 1; i < countOfSub; i++) { print2Console(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 - BOTTOM_BORDER.length()) { print2Console(type, tag, PLACEHOLDER + LINE_SEP + TOP_BORDER + LINE_SEP + LEFT_BORDER + msg.substring(index, len)); } } else { print2Console(type, tag, msg.substring(0, MAX_LEN)); int index = MAX_LEN; for (int i = 1; i < countOfSub; i++) { print2Console(type, tag, PLACEHOLDER + LINE_SEP + msg.substring(index, index + MAX_LEN)); index += MAX_LEN; } if (index != len) { print2Console(type, tag, PLACEHOLDER + LINE_SEP + msg.substring(index, len)); } } } else { print2Console(type, tag, msg); } } private static void print2Console(int type, String tag, String msg) { Log.println(type, tag, msg); if (CONFIG.mOnConsoleOutputListener != null) { CONFIG.mOnConsoleOutputListener.onConsoleOutput(type, tag, msg); } } private static void print2File(final int type, final String tag, final String msg) { Date d = new Date(); String format = getSdf().format(d); String date = format.substring(0, 10); String currentLogFilePath = getCurrentLogFilePath(d); if (!createOrExistsFile(currentLogFilePath, date)) { Log.e("LogUtils", "create " + currentLogFilePath + " failed!"); return; } String time = format.substring(11); final String content = time + T[type - V] + "/" + tag + msg + LINE_SEP; input2File(currentLogFilePath, content); } private static String getCurrentLogFilePath(Date d) { String format = getSdf().format(d); String date = format.substring(0, 10); return CONFIG.getDir() + CONFIG.getFilePrefix() + "_" + date + "_" + CONFIG.getProcessName() + CONFIG.getFileExtension(); } private static SimpleDateFormat getSdf() { if (simpleDateFormat == null) { simpleDateFormat = new SimpleDateFormat("yyyy_MM_dd HH:mm:ss.SSS ", Locale.getDefault()); } return simpleDateFormat; } private static boolean createOrExistsFile(final String filePath, final String date) { File file = new File(filePath); if (file.exists()) return file.isFile(); if (!UtilsBridge.createOrExistsDir(file.getParentFile())) return false; try { deleteDueLogs(filePath, date); boolean isCreate = file.createNewFile(); if (isCreate) { printDeviceInfo(filePath, date); } return isCreate; } catch (IOException e) { e.printStackTrace(); return false; } } private static void deleteDueLogs(final String filePath, final String date) { if (CONFIG.getSaveDays() <= 0) return; File file = new File(filePath); File parentFile = file.getParentFile(); File[] files = parentFile.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return isMatchLogFileName(name); } }); if (files == null || files.length <= 0) return; final SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd", Locale.getDefault()); try { long dueMillis = sdf.parse(date).getTime() - CONFIG.getSaveDays() * 86400000L; for (final File aFile : files) { String name = aFile.getName(); int l = name.length(); String logDay = findDate(name); if (sdf.parse(logDay).getTime() <= dueMillis) { EXECUTOR.execute(new Runnable() { @Override public void run() { boolean delete = aFile.delete(); if (!delete) { Log.e("LogUtils", "delete " + aFile + " failed!"); } } }); } } } catch (ParseException e) { e.printStackTrace(); } } private static boolean isMatchLogFileName(String name) { return name.matches("^" + CONFIG.getFilePrefix() + "_[0-9]{4}_[0-9]{2}_[0-9]{2}_.*$"); } private static String findDate(String str) { Pattern pattern = Pattern.compile("[0-9]{4}_[0-9]{2}_[0-9]{2}"); Matcher matcher = pattern.matcher(str); if (matcher.find()) { return matcher.group(); } return ""; } private static void printDeviceInfo(final String filePath, final String date) { CONFIG.mFileHead.addFirst("Date of Log", date); input2File(filePath, CONFIG.mFileHead.toString()); } private static void input2File(final String filePath, final String input) { if (CONFIG.mFileWriter == null) { UtilsBridge.writeFileFromString(filePath, input, true); } else { CONFIG.mFileWriter.write(filePath, input); } if (CONFIG.mOnFileOutputListener != null) { CONFIG.mOnFileOutputListener.onFileOutput(filePath, input); } } public static final class Config { private String mDefaultDir; // The default storage directory of log. private String mDir; // The storage directory of log. private String mFilePrefix = "util";// The file prefix of log. private String mFileExtension = ".txt";// The file extension of log. private boolean mLogSwitch = true; // The switch of log. private boolean mLog2ConsoleSwitch = true; // The logcat's switch of log. private String mGlobalTag = ""; // The global tag of log. private boolean mTagIsSpace = true; // The global tag is space. private boolean mLogHeadSwitch = true; // The head's switch of log. private boolean mLog2FileSwitch = false; // The file's switch of log. private boolean mLogBorderSwitch = true; // The border's switch of log. private boolean mSingleTagSwitch = true; // The single tag of log. private int mConsoleFilter = V; // The console's filter of log. private int mFileFilter = V; // The file's filter of log. private int mStackDeep = 1; // The stack's deep of log. private int mStackOffset = 0; // The stack's offset of log. private int mSaveDays = -1; // The save days of log. private String mProcessName = UtilsBridge.getCurrentProcessName(); private IFileWriter mFileWriter; private OnConsoleOutputListener mOnConsoleOutputListener; private OnFileOutputListener mOnFileOutputListener; private UtilsBridge.FileHead mFileHead = new UtilsBridge.FileHead("Log"); private Config() { if (UtilsBridge.isSDCardEnableByEnvironment() && Utils.getApp().getExternalFilesDir(null) != null) mDefaultDir = Utils.getApp().getExternalFilesDir(null) + FILE_SEP + "log" + FILE_SEP; else { mDefaultDir = Utils.getApp().getFilesDir() + FILE_SEP + "log" + FILE_SEP; } } public final Config setLogSwitch(final boolean logSwitch) { mLogSwitch = logSwitch; return this; } public final Config setConsoleSwitch(final boolean consoleSwitch) { mLog2ConsoleSwitch = consoleSwitch; return this; } public final Config setGlobalTag(final String tag) { if (UtilsBridge.isSpace(tag)) { mGlobalTag = ""; mTagIsSpace = true; } else { mGlobalTag = tag; mTagIsSpace = false; } return this; } public final Config setLogHeadSwitch(final boolean logHeadSwitch) { mLogHeadSwitch = logHeadSwitch; return this; } public final Config setLog2FileSwitch(final boolean log2FileSwitch) { mLog2FileSwitch = log2FileSwitch; return this; } public final Config setDir(final String dir) { if (UtilsBridge.isSpace(dir)) { mDir = null; } else { mDir = dir.endsWith(FILE_SEP) ? dir : dir + FILE_SEP; } return this; } public final Config setDir(final File dir) { mDir = dir == null ? null : (dir.getAbsolutePath() + FILE_SEP); return this; } public final Config setFilePrefix(final String filePrefix) { if (UtilsBridge.isSpace(filePrefix)) { mFilePrefix = "util"; } else { mFilePrefix = filePrefix; } return this; } public final Config setFileExtension(final String fileExtension) { if (UtilsBridge.isSpace(fileExtension)) { mFileExtension = ".txt"; } else { if (fileExtension.startsWith(".")) { mFileExtension = fileExtension; } else { mFileExtension = "." + fileExtension; } } return this; } public final Config setBorderSwitch(final boolean borderSwitch) { mLogBorderSwitch = borderSwitch; return this; } public final Config setSingleTagSwitch(final boolean singleTagSwitch) { mSingleTagSwitch = singleTagSwitch; return this; } public final Config setConsoleFilter(@TYPE final int consoleFilter) { mConsoleFilter = consoleFilter; return this; } public final Config setFileFilter(@TYPE final int fileFilter) { mFileFilter = fileFilter; return this; } public final Config setStackDeep(@IntRange(from = 1) final int stackDeep) { mStackDeep = stackDeep; return this; } public final Config setStackOffset(@IntRange(from = 0) final int stackOffset) { mStackOffset = stackOffset; return this; } public final Config setSaveDays(@IntRange(from = 1) final int saveDays) { mSaveDays = saveDays; return this; } public final Config addFormatter(final IFormatter iFormatter) { if (iFormatter != null) { I_FORMATTER_MAP.put(getTypeClassFromParadigm(iFormatter), iFormatter); } return this; } public final Config setFileWriter(final IFileWriter fileWriter) { mFileWriter = fileWriter; return this; } public final Config setOnConsoleOutputListener(final OnConsoleOutputListener listener) { mOnConsoleOutputListener = listener; return this; } public final Config setOnFileOutputListener(final OnFileOutputListener listener) { mOnFileOutputListener = listener; return this; } public final Config addFileExtraHead(final Map fileExtraHead) { mFileHead.append(fileExtraHead); return this; } public final Config addFileExtraHead(final String key, final String value) { mFileHead.append(key, value); return this; } public final String getProcessName() { if (mProcessName == null) return ""; return mProcessName.replace(":", "_"); } public final String getDefaultDir() { return mDefaultDir; } public final String getDir() { return mDir == null ? mDefaultDir : mDir; } public final String getFilePrefix() { return mFilePrefix; } public final String getFileExtension() { return mFileExtension; } public final boolean isLogSwitch() { return mLogSwitch; } public final boolean isLog2ConsoleSwitch() { return mLog2ConsoleSwitch; } public final String getGlobalTag() { if (UtilsBridge.isSpace(mGlobalTag)) return ""; return mGlobalTag; } public final boolean isLogHeadSwitch() { return mLogHeadSwitch; } public final boolean isLog2FileSwitch() { return mLog2FileSwitch; } public final boolean isLogBorderSwitch() { return mLogBorderSwitch; } public final boolean isSingleTagSwitch() { return mSingleTagSwitch; } public final char getConsoleFilter() { return T[mConsoleFilter - V]; } public final char getFileFilter() { return T[mFileFilter - V]; } public final int getStackDeep() { return mStackDeep; } public final int getStackOffset() { return mStackOffset; } public final int getSaveDays() { return mSaveDays; } public final boolean haveSetOnConsoleOutputListener() { return mOnConsoleOutputListener != null; } public final boolean haveSetOnFileOutputListener() { return mOnFileOutputListener != null; } @Override public String toString() { return "process: " + getProcessName() + LINE_SEP + "logSwitch: " + isLogSwitch() + LINE_SEP + "consoleSwitch: " + isLog2ConsoleSwitch() + LINE_SEP + "tag: " + (getGlobalTag().equals("") ? "null" : getGlobalTag()) + LINE_SEP + "headSwitch: " + isLogHeadSwitch() + LINE_SEP + "fileSwitch: " + isLog2FileSwitch() + LINE_SEP + "dir: " + getDir() + LINE_SEP + "filePrefix: " + getFilePrefix() + LINE_SEP + "borderSwitch: " + isLogBorderSwitch() + LINE_SEP + "singleTagSwitch: " + isSingleTagSwitch() + LINE_SEP + "consoleFilter: " + getConsoleFilter() + LINE_SEP + "fileFilter: " + getFileFilter() + LINE_SEP + "stackDeep: " + getStackDeep() + LINE_SEP + "stackOffset: " + getStackOffset() + LINE_SEP + "saveDays: " + getSaveDays() + LINE_SEP + "formatter: " + I_FORMATTER_MAP + LINE_SEP + "fileWriter: " + mFileWriter + LINE_SEP + "onConsoleOutputListener: " + mOnConsoleOutputListener + LINE_SEP + "onFileOutputListener: " + mOnFileOutputListener + LINE_SEP + "fileExtraHeader: " + mFileHead.getAppended(); } } public abstract static class IFormatter { public abstract String format(T t); } public interface IFileWriter { void write(String file, String content); } public interface OnConsoleOutputListener { void onConsoleOutput(@TYPE int type, String tag, String content); } public interface OnFileOutputListener { void onFileOutput(String filePath, String content); } private final static class TagHead { String tag; String[] consoleHead; String fileHead; TagHead(String tag, String[] consoleHead, String fileHead) { this.tag = tag; this.consoleHead = consoleHead; this.fileHead = fileHead; } } private final static class LogFormatter { static String object2String(Object object) { return object2String(object, -1); } static String object2String(Object object, int type) { if (object.getClass().isArray()) return array2String(object); if (object instanceof Throwable) return UtilsBridge.getFullStackTrace((Throwable) object); if (object instanceof Bundle) return bundle2String((Bundle) object); if (object instanceof Intent) return intent2String((Intent) object); if (type == JSON) { return object2Json(object); } else if (type == XML) { return formatXml(object.toString()); } return object.toString(); } private static String bundle2String(Bundle bundle) { Iterator iterator = bundle.keySet().iterator(); if (!iterator.hasNext()) { return "Bundle {}"; } StringBuilder sb = new StringBuilder(128); sb.append("Bundle { "); for (; ; ) { String key = iterator.next(); Object value = bundle.get(key); sb.append(key).append('='); if (value instanceof Bundle) { sb.append(value == bundle ? "(this Bundle)" : bundle2String((Bundle) value)); } else { sb.append(formatObject(value)); } if (!iterator.hasNext()) return sb.append(" }").toString(); sb.append(',').append(' '); } } private static String intent2String(Intent intent) { StringBuilder sb = new StringBuilder(128); sb.append("Intent { "); boolean first = true; String mAction = intent.getAction(); if (mAction != null) { sb.append("act=").append(mAction); first = false; } Set mCategories = intent.getCategories(); if (mCategories != null) { if (!first) { sb.append(' '); } first = false; sb.append("cat=["); boolean firstCategory = true; for (String c : mCategories) { if (!firstCategory) { sb.append(','); } sb.append(c); firstCategory = false; } sb.append("]"); } Uri mData = intent.getData(); if (mData != null) { if (!first) { sb.append(' '); } first = false; sb.append("dat=").append(mData); } String mType = intent.getType(); if (mType != null) { if (!first) { sb.append(' '); } first = false; sb.append("typ=").append(mType); } int mFlags = intent.getFlags(); if (mFlags != 0) { if (!first) { sb.append(' '); } first = false; sb.append("flg=0x").append(Integer.toHexString(mFlags)); } String mPackage = intent.getPackage(); if (mPackage != null) { if (!first) { sb.append(' '); } first = false; sb.append("pkg=").append(mPackage); } ComponentName mComponent = intent.getComponent(); if (mComponent != null) { if (!first) { sb.append(' '); } first = false; sb.append("cmp=").append(mComponent.flattenToShortString()); } Rect mSourceBounds = intent.getSourceBounds(); 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) { ClipData mClipData = intent.getClipData(); if (mClipData != null) { if (!first) { sb.append(' '); } first = false; clipData2String(mClipData, sb); } } Bundle mExtras = intent.getExtras(); 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) { Intent mSelector = intent.getSelector(); if (mSelector != null) { if (!first) { sb.append(' '); } first = false; sb.append("sel={"); sb.append(mSelector == intent ? "(this Intent)" : intent2String(mSelector)); sb.append("}"); } } sb.append(" }"); return sb.toString(); } @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) private static void clipData2String(ClipData clipData, StringBuilder sb) { ClipData.Item item = clipData.getItemAt(0); if (item == null) { sb.append("ClipData.Item {}"); return; } sb.append("ClipData.Item { "); String mHtmlText = item.getHtmlText(); if (mHtmlText != null) { sb.append("H:"); sb.append(mHtmlText); sb.append("}"); return; } CharSequence mText = item.getText(); if (mText != null) { sb.append("T:"); sb.append(mText); sb.append("}"); return; } Uri uri = item.getUri(); if (uri != null) { sb.append("U:").append(uri); sb.append("}"); return; } Intent intent = item.getIntent(); if (intent != null) { sb.append("I:"); sb.append(intent2String(intent)); sb.append("}"); return; } sb.append("NULL"); sb.append("}"); } private static String object2Json(Object object) { if (object instanceof CharSequence) { return UtilsBridge.formatJson(object.toString()); } try { return UtilsBridge.getGson4LogUtils().toJson(object); } catch (Throwable t) { return object.toString(); } } private static String formatJson(String json) { try { for (int i = 0, len = json.length(); i < len; i++) { char c = json.charAt(i); if (c == '{') { return new JSONObject(json).toString(2); } else if (c == '[') { return new JSONArray(json).toString(2); } else if (!Character.isWhitespace(c)) { return json; } } } catch (JSONException e) { e.printStackTrace(); } return json; } private static String formatXml(String xml) { try { Source xmlInput = new StreamSource(new StringReader(xml)); StreamResult xmlOutput = new StreamResult(new StringWriter()); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); transformer.transform(xmlInput, xmlOutput); xml = xmlOutput.getWriter().toString().replaceFirst(">", ">" + LINE_SEP); } catch (Exception e) { e.printStackTrace(); } return xml; } private static String 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 Class getTypeClassFromParadigm(final IFormatter formatter) { Type[] genericInterfaces = formatter.getClass().getGenericInterfaces(); Type type; if (genericInterfaces.length == 1) { type = genericInterfaces[0]; } else { type = formatter.getClass().getGenericSuperclass(); } type = ((ParameterizedType) type).getActualTypeArguments()[0]; while (type instanceof ParameterizedType) { type = ((ParameterizedType) type).getRawType(); } String 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 (ClassNotFoundException e) { e.printStackTrace(); } return null; } private static Class getClassFromObject(final Object obj) { Class objClass = obj.getClass(); if (objClass.isAnonymousClass() || objClass.isSynthetic()) { Type[] genericInterfaces = objClass.getGenericInterfaces(); String className; if (genericInterfaces.length == 1) {// interface Type type = genericInterfaces[0]; while (type instanceof ParameterizedType) { type = ((ParameterizedType) type).getRawType(); } className = type.toString(); } else {// abstract class or lambda Type type = objClass.getGenericSuperclass(); while (type instanceof ParameterizedType) { type = ((ParameterizedType) type).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 (ClassNotFoundException e) { e.printStackTrace(); } } return objClass; } }