public class LoggerConstants {

    public static final String DATABASE_NAME = "logful.db";

    /**
     * 日志文件存储文件夹名称.
     */
    public static final String LOG_DIR_NAME = "log";

    /**
     * 崩溃日志文件存储文件夹.
     */
    public static final String CRASH_REPORT_DIR_NAME = "crash";

    /**
     * 附件截图文件夹名称.
     */
    public static final String ATTACHMENT_DIR_NAME = "attachment";

    /**
     * 本地配置文件名称.
     */
    public static final String CONFIG_FILE_NAME = "logful.config";

    public static final String VERBOSE_NAME = "verbose";

    public static final String DEBUG_NAME = "debug";

    public static final String INFO_NAME = "info";

    public static final String WARN_NAME = "warn";

    public static final String ERROR_NAME = "error";

    public static final String EXCEPTION_NAME = "exception";

    public static final String FATAL_NAME = "fatal";

    public static String getLogLevelName(int level) {
        switch (level) {
            case Constants.VERBOSE:
                return VERBOSE_NAME;
            case Constants.DEBUG:
                return DEBUG_NAME;
            case Constants.INFO:
                return INFO_NAME;
            case Constants.WARN:
                return WARN_NAME;
            case Constants.ERROR:
                return ERROR_NAME;
            case Constants.EXCEPTION:
                return EXCEPTION_NAME;
            case Constants.FATAL:
                return FATAL_NAME;
            default:
                return VERBOSE_NAME;
        }
    }

    public static final int DEFAULT_ACTIVE_UPLOAD_TASK = 2;

    public static final int DEFAULT_ACTIVE_LOG_WRITER = 2;

    public static final boolean DEFAULT_DELETE_UPLOADED_LOG_FILE = false;

    public static final boolean DEFAULT_CAUGHT_EXCEPTION = false;

    public static final int[] DEFAULT_UPLOAD_NETWORK_TYPE = {Constants.TYPE_WIFI, Constants.TYPE_MOBILE};

    public static final int[] DEFAULT_UPLOAD_LOG_LEVEL = {Constants.VERBOSE, Constants.DEBUG, Constants.INFO,
            Constants.WARN, Constants.ERROR, Constants.EXCEPTION, Constants.FATAL};

    public static final long DEFAULT_LOG_FILE_MAX_SIZE = 524288;

    public static final long DEFAULT_UPDATE_SYSTEM_FREQUENCY = 3600;

    public static final String DEFAULT_LOGGER_NAME = "app";

    public static final String DEFAULT_MSG_LAYOUT = "";

    public static final String API_BASE_URL = "http://demo.logful.aoapp.com:9600";

    public static final String CLIENT_AUTH_URI = "/oauth/token";

    public static final String UPLOAD_USER_INFO_URI = "/log/info/upload";

    public static final String BIND_DEVICE_ID_URI = "/log/bind";

    public static final String UPLOAD_LOG_FILE_URI = "/log/file/upload";

    public static final String UPLOAD_CRASH_REPORT_FILE_URI = "/log/crash/upload";

    public static final String UPLOAD_ATTACHMENT_FILE_URI = "/log/attachment/upload";

    public static final int DEFAULT_HTTP_REQUEST_TIMEOUT = 6000;

    public static final String CHARSET = "UTF-8";

    public static final String VERSION = "0.3.0";

    public static final int STATE_ALL = 0x00;

    public static final int STATE_NORMAL = 0x01;

    public static final int STATE_WILL_UPLOAD = 0x02;

    public static final int STATE_UPLOADED = 0x03;

    public static final int STATE_DELETED = 0x04;

    public static final int LOCATION_EXTERNAL = 0x01;

    public static final int LOCATION_INTERNAL = 0x02;

    public static final int DEFAULT_SCREENSHOT_QUALITY = 80;

    public static final float DEFAULT_SCREENSHOT_SCALE = 0.5f;

    public static final boolean DEFAULT_USE_NATIVE_CRYPTOR = true;

    public static final int PLATFORM_ANDROID = 1;

    public static final String QUERY_PARAM_SDK_VERSION = "sdk";

    public static final String QUERY_PARAM_PLATFORM = "platform";

    public static final String QUERY_PARAM_UID = "uid";
} public class LoggerFactory {

    private static final String TAG = "LoggerFactory";

    private static Application loggerApplication;

    private static Context loggerContext;

    private static LoggerConfigurator loggerConfiguration;

    private static final Lock lock = new ReentrantLock();

    private static String annotationLoggerName;

    private static String annotationMsgLayout;

    private static boolean initialized = false;

    private static boolean DEBUG = false;

    private static final ConcurrentHashMap<String, Logger> loggerCache = new ConcurrentHashMap<String, Logger>();

    /**
     * LoggerFactory init.
     *
     * @param activity Activity
     */
    public static void init(Activity activity) {
        LoggerFactory.init(activity.getApplication());
    }

    /**
     * LoggerFactory init.
     *
     * @param activity      Activity
     * @param configuration LoggerConfigurator
     */
    public static void init(Activity activity, LoggerConfigurator configuration) {
        LoggerFactory.init(activity.getApplication(), configuration);
    }

    /**
     * LoggerFactory init.
     *
     * @param application Application
     */
    public static void init(Application application) {
        LoggerConfigurator configuration = LoggerConfigurator.newBuilder().build();
        LoggerFactory.init(application, configuration);
    }

    /**
     * LoggerFactory init.
     *
     * @param application   Application
     * @param configuration LoggerConfigurator
     */
    public static void init(Application application, LoggerConfigurator configuration) {
        lock.lock();

        if (LoggerFactory.initialized) {
            lock.unlock();
            LogUtil.w(TAG, "LoggerFactory can only initialize once.");
            return;
        }

        Class<?> applicationClass = application.getClass();
        LogProperties logProperties = applicationClass.getAnnotation(LogProperties.class);
        if (logProperties != null) {
            annotationLoggerName = logProperties.defaultLogger();
            annotationMsgLayout = logProperties.defaultMsgLayout();
        }

        LoggerFactory.loggerApplication = application;
        LoggerFactory.loggerContext = application.getApplicationContext();
        LoggerFactory.loggerConfiguration = configuration;

        // 初始化数据库.
        DatabaseManager.manager();

        // 读取存储的配置信息.
        LogfulConfigurer.config().setFrequency(configuration.getUpdateSystemFrequency(), false, true);

        if (configuration.isCaughtException()) {
            // 捕捉未捕捉到的异常信息.
            CrashReporter.caught();
        }

        // 用户授权.
        ClientUserInitService.authenticate();

        LoggerFactory.initialized = true;

        // 读取缓存日志.
        if (LogfulConfigurer.config().on()) {
            AsyncAppenderManager.readCache();
        }

        lock.unlock();
    }

    private static String defaultLoggerName() {
        if (loggerConfiguration != null
                && !StringUtils.isEmpty(loggerConfiguration.getDefaultLoggerName())) {
            return loggerConfiguration.getDefaultLoggerName();
        } else {
            if (!StringUtils.isEmpty(annotationLoggerName)) {
                return annotationLoggerName;
            }
        }
        return LoggerConstants.DEFAULT_LOGGER_NAME;
    }

    private static String defaultMsgLayout() {
        if (loggerConfiguration != null
                && !StringUtils.isEmpty(loggerConfiguration.getDefaultMsgLayout())) {
            return loggerConfiguration.getDefaultMsgLayout();
        } else {
            if (!StringUtils.isEmpty(annotationMsgLayout)) {
                return annotationMsgLayout;
            }
        }
        return LoggerConstants.DEFAULT_MSG_LAYOUT;
    }

    public static Application application() {
        if (loggerApplication != null) {
            return loggerApplication;
        }
        return null;
    }

    public static Context context() {
        return loggerContext;
    }

    public static void setDebug(boolean debug) {
        LoggerFactory.DEBUG = debug;
    }

    public static boolean isDebug() {
        return LoggerFactory.DEBUG;
    }

    /**
     * Set api base url.
     *
     * @param apiUrl Api base url string
     */
    public static void setApiUrl(String apiUrl) {
        SystemConfig.saveBaseUrl(apiUrl);
    }

    /**
     * Set app key.
     *
     * @param appKey App key string
     */
    public static void setAppKey(String appKey) {
        SystemConfig.saveAppKey(appKey);
    }

    /**
     * Set app secret.
     *
     * @param appSecret App secret string
     */
    public static void setAppSecret(String appSecret) {
        SystemConfig.saveAppSecret(appSecret);
    }

    /**
     * 获取当前日志库版本.
     *
     * @return version string
     */
    public static String version() {
        return LoggerConstants.VERSION;
    }

    public static LoggerConfigurator config() {
        return loggerConfiguration;
    }

    /**
     * 根据名称获取 logger.
     *
     * @param loggerName Logger name
     * @return Logger instance
     */
    public static Logger logger(String loggerName) {
        Logger logger = loggerCache.get(loggerName);
        if (logger == null) {
            logger = new DefaultLogger(loggerName);
            // Set default msg layout
            logger.setMsgLayout(LoggerFactory.defaultMsgLayout());
            loggerCache.put(logger.getName(), logger);
        }
        return logger;
    }

    /**
     * 绑定用户别名.
     *
     * @param alias User alias
     */
    public static void bindAlias(String alias) {
        if (initialized) {
            SystemConfig.saveAlias(alias);
        }
    }

    /**
     * 打开日志记录.
     */
    public static void turnOnLog() {
        if (initialized) {
            LogfulConfigurer.config().setOn(true, true, true);
            // Read pre log event cache.
            AsyncAppenderManager.readCache();
        }
    }

    /**
     * 关闭日志记录.
     */
    public static void turnOffLog() {
        if (initialized) {
            LogfulConfigurer.config().setOn(false, true, true);
        }
    }

    /**
     * 获取当前日志状态.
     *
     * @return 是否打开
     */
    public static boolean isOn() {
        return initialized && LogfulConfigurer.config().on();
    }

    /**
     * 同步日志文件.
     */
    public static void syncLog() {
        if (initialized) {
            TransferManager.uploadLogFile();
            TransferManager.uploadAttachment();
        }
    }

    /**
     * 同步日志文件.
     *
     * @param startTime 需要同步的日志文件开始记录时间
     * @param endTime   需要同步的日志文件结束记录时间
     */
    public static void syncLog(long startTime, long endTime) {
        if (initialized) {
            TransferManager.uploadLogFile(startTime, endTime);
            TransferManager.uploadAttachment();
        }
    }

    /**
     * 中断当前所有正在写入的日志文件并立即上传.
     */
    public static void interruptThenSync() {
        if (initialized) {
            AsyncAppenderManager.interrupt();
            TransferManager.uploadLogFile();
            TransferManager.uploadAttachment();
            TransferManager.uploadCrashReport();
        }
    }

    /**
     * 解析推送控制日志动作链.
     *
     * @param data 动作链内容
     */
    public static void parseTransmission(String data) {
        LogUtil.i(TAG, "Receive payload: " + data);
        if (initialized) {
            try {
                JSONObject object = new JSONObject(data);
                if (object.has("logful")) {
                    LogfulConfigurer.config().parse(object.getJSONObject("logful"), true, true);
                }
            } catch (Exception e) {
                LogUtil.e(TAG, "", e);
            }
        }
    }

    /**
     * 使用默认的 logger 打印 verbose 信息.
     *
     * @param tag Tag
     * @param msg Message
     */
    public static void verbose(String tag, String msg) {
        Logger defaultLogger = LoggerFactory.logger(LoggerFactory.defaultLoggerName());
        defaultLogger.verbose(tag, msg);
    }

    /**
     * 使用默认的 logger 打印 verbose 信息.
     *
     * @param tag     Tag
     * @param msg     Message
     * @param capture Capture screen
     */
    public static void verbose(String tag, String msg, boolean capture) {
        Logger defaultLogger = LoggerFactory.logger(LoggerFactory.defaultLoggerName());
        defaultLogger.verbose(tag, msg, capture);
    }

    /**
     * 使用默认的 logger 打印 debug 信息.
     *
     * @param tag Tag
     * @param msg Message
     */
    public static void debug(String tag, String msg) {
        Logger defaultLogger = LoggerFactory.logger(LoggerFactory.defaultLoggerName());
        defaultLogger.debug(tag, msg);
    }

    /**
     * 使用默认的 logger 打印 debug 信息.
     *
     * @param tag     Tag
     * @param msg     Message
     * @param capture Capture screen
     */
    public static void debug(String tag, String msg, boolean capture) {
        Logger defaultLogger = LoggerFactory.logger(LoggerFactory.defaultLoggerName());
        defaultLogger.debug(tag, msg, capture);
    }

    /**
     * 使用默认的 logger 打印 info 信息.
     *
     * @param tag Tag
     * @param msg Message
     */
    public static void info(String tag, String msg) {
        Logger defaultLogger = LoggerFactory.logger(LoggerFactory.defaultLoggerName());
        defaultLogger.info(tag, msg);
    }

    /**
     * 使用默认的 logger 打印 info 信息.
     *
     * @param tag     Tag
     * @param msg     Message
     * @param capture Capture screen
     */
    public static void info(String tag, String msg, boolean capture) {
        Logger defaultLogger = LoggerFactory.logger(LoggerFactory.defaultLoggerName());
        defaultLogger.info(tag, msg, capture);
    }

    /**
     * 使用默认的 logger 打印 warn 信息.
     *
     * @param tag Tag
     * @param msg Message
     */
    public static void warn(String tag, String msg) {
        Logger defaultLogger = LoggerFactory.logger(LoggerFactory.defaultLoggerName());
        defaultLogger.warn(tag, msg);
    }

    /**
     * 使用默认的 logger 打印 warn 信息.
     *
     * @param tag     Tag
     * @param msg     Message
     * @param capture Capture screen
     */
    public static void warn(String tag, String msg, boolean capture) {
        Logger defaultLogger = LoggerFactory.logger(LoggerFactory.defaultLoggerName());
        defaultLogger.warn(tag, msg, capture);
    }

    /**
     * 使用默认的 logger 打印 error 信息.
     *
     * @param tag Tag
     * @param msg Message
     */
    public static void error(String tag, String msg) {
        Logger defaultLogger = LoggerFactory.logger(LoggerFactory.defaultLoggerName());
        defaultLogger.error(tag, msg);
    }

    /**
     * 使用默认的 logger 打印 error 信息.
     *
     * @param tag     Tag
     * @param msg     Message
     * @param capture Capture screen
     */
    public static void error(String tag, String msg, boolean capture) {
        Logger defaultLogger = LoggerFactory.logger(LoggerFactory.defaultLoggerName());
        defaultLogger.error(tag, msg, capture);
    }

    /**
     * 使用默认的 logger 打印 exception 信息.
     *
     * @param tag       Tag
     * @param msg       Message
     * @param throwable Throwable
     */
    public static void exception(String tag, String msg, Throwable throwable) {
        Logger defaultLogger = LoggerFactory.logger(LoggerFactory.defaultLoggerName());
        defaultLogger.exception(tag, msg, throwable);
    }

    /**
     * 使用默认的 logger 打印 exception 信息.
     *
     * @param tag       Tag
     * @param msg       Message
     * @param throwable Throwable
     * @param capture   Capture screen
     */
    public static void exception(String tag, String msg, Throwable throwable, boolean capture) {
        Logger defaultLogger = LoggerFactory.logger(LoggerFactory.defaultLoggerName());
        defaultLogger.exception(tag, msg, throwable, capture);
    }

    /**
     * 使用默认的 logger 打印 fatal 信息.
     *
     * @param tag Tag
     * @param msg Message
     */
    public static void fatal(String tag, String msg) {
        Logger defaultLogger = LoggerFactory.logger(LoggerFactory.defaultLoggerName());
        defaultLogger.fatal(tag, msg);
    }

    /**
     * 使用默认的 logger 打印 fatal 信息.
     *
     * @param tag     Tag
     * @param msg     Message
     * @param capture Capture screen
     */
    public static void fatal(String tag, String msg, boolean capture) {
        Logger defaultLogger = LoggerFactory.logger(LoggerFactory.defaultLoggerName());
        defaultLogger.fatal(tag, msg, capture);
    }
} public class DatabaseManager extends SQLiteOpenHelper {

    private static final String TABLE_LOG_FILE_META = "logful_log_meta";
    private static final String TABLE_CRASH_REPORT_FILE_META = "logful_crash_meta";
    private static final String TABLE_MSG_LAYOUT = "logful_layout";
    private static final String TABLE_ATTACHMENT_FILE_META = "logful_attachment_meta";

    // Log file meta table field.
    private static final String LOG_FIELD_ID = "id";
    private static final String LOG_FIELD_LOGGER_NAME = "logger_name";
    private static final String LOG_FIELD_FILENAME = "filename";
    private static final String LOG_FIELD_FRAGMENT = "fragment";
    private static final String LOG_FIELD_DATE_STRING = "date";
    private static final String LOG_FIELD_STATUS = "status";
    private static final String LOG_FIELD_MD5 = "md5";
    private static final String LOG_FIELD_LOCATION = "location";
    private static final String LOG_FIELD_LEVEL = "level";
    private static final String LOG_FIELD_EOF = "eof";
    private static final String LOG_FIELD_CREATE_TIME = "create_time";
    private static final String LOG_FIELD_DELETE_TIME = "delete_time";

    // Crash report file meta table field.
    private static final String CRASH_FIELD_ID = "id";
    private static final String CRASH_FIELD_FILENAME = "filename";
    private static final String CRASH_FIELD_LOCATION = "location";
    private static final String CRASH_FIELD_CREATE_TIME = "create_time";
    private static final String CRASH_FIELD_DELETE_TIME = "delete_time";
    private static final String CRASH_FIELD_STATUS = "status";
    private static final String CRASH_FIELD_FILE_MD5 = "md5";

    // Msg layout table field.
    private static final String MSG_LAYOUT_FIELD_ID = "id";
    private static final String MSG_LAYOUT_FIELD_LAYOUT = "layout";

    // Attachment file meta table field.
    private static final String ATTACHMENT_FIELD_ID = "id";
    private static final String ATTACHMENT_FIELD_FILENAME = "filename";
    private static final String ATTACHMENT_FIELD_LOCATION = "location";
    private static final String ATTACHMENT_FIELD_SEQUENCE = "sequence";
    private static final String ATTACHMENT_FIELD_CREATE_TIME = "create_time";
    private static final String ATTACHMENT_FIELD_DELETE_TIME = "delete_time";
    private static final String ATTACHMENT_FIELD_STATUS = "status";
    private static final String ATTACHMENT_FIELD_FILE_MD5 = "md5";

    private static final String AND = " AND ";

    private static final int SCHEMA_VERSION = 18;

    private static ConcurrentHashMap<String, Short> layoutMap = new ConcurrentHashMap<String, Short>();

    private static DatabaseManager instance;

    public static synchronized DatabaseManager manager() {
        if (instance == null) {
            Context context = LoggerFactory.context();
            if (context != null) {
                instance = new DatabaseManager(context.getApplicationContext());
            }
        }
        return instance;
    }

    private DatabaseManager(Context context) {
        super(context, LoggerConstants.DATABASE_NAME, null, SCHEMA_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String text = " TEXT,";
        String integer = " INTEGER,";

        // Create log file meta table.
        String createLogFileMetaTable =
                "CREATE TABLE " + TABLE_LOG_FILE_META + "(" + LOG_FIELD_ID + " INTEGER PRIMARY KEY,"
                        + LOG_FIELD_LOGGER_NAME + text + LOG_FIELD_FILENAME + " TEXT NOT NULL," + LOG_FIELD_FRAGMENT
                        + integer + LOG_FIELD_DATE_STRING + text + LOG_FIELD_STATUS + integer + LOG_FIELD_MD5 + text
                        + LOG_FIELD_LOCATION + integer + LOG_FIELD_LEVEL + integer + LOG_FIELD_EOF + integer
                        + LOG_FIELD_CREATE_TIME + integer + LOG_FIELD_DELETE_TIME + " INTEGER" + ")";

        // Create crash report file meta table.
        String createCrashReportFileMetaTable =
                "CREATE TABLE " + TABLE_CRASH_REPORT_FILE_META + "(" + CRASH_FIELD_ID + " INTEGER PRIMARY KEY,"
                        + CRASH_FIELD_FILENAME + text + CRASH_FIELD_LOCATION + integer
                        + CRASH_FIELD_CREATE_TIME + integer + CRASH_FIELD_DELETE_TIME + integer + CRASH_FIELD_STATUS + integer
                        + CRASH_FIELD_FILE_MD5 + " TEXT" + ")";

        // Create msg layout table.
        String createMsgLayoutTable =
                "CREATE TABLE " + TABLE_MSG_LAYOUT + "(" + MSG_LAYOUT_FIELD_ID + " INTEGER PRIMARY KEY,"
                        + MSG_LAYOUT_FIELD_LAYOUT + " TEXT NOT NULL UNIQUE" + ")";

        // Create attachment file meta table.
        String createAttachmentTable =
                "CREATE TABLE " + TABLE_ATTACHMENT_FILE_META + "(" + ATTACHMENT_FIELD_ID + " INTEGER PRIMARY KEY,"
                        + ATTACHMENT_FIELD_FILENAME + text + ATTACHMENT_FIELD_LOCATION + integer
                        + ATTACHMENT_FIELD_SEQUENCE + integer + ATTACHMENT_FIELD_CREATE_TIME + integer
                        + ATTACHMENT_FIELD_DELETE_TIME + integer + ATTACHMENT_FIELD_STATUS + integer
                        + ATTACHMENT_FIELD_FILE_MD5 + " TEXT" + ")";

        db.execSQL(createLogFileMetaTable);
        db.execSQL(createCrashReportFileMetaTable);
        db.execSQL(createMsgLayoutTable);
        db.execSQL(createAttachmentTable);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_LOG_FILE_META);
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_ATTACHMENT_FILE_META);
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_MSG_LAYOUT);
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_CRASH_REPORT_FILE_META);
        onCreate(db);
    }

    /**
     * Get layout string id.
     *
     * @param layoutString Layout string
     * @return Layout id
     */
    public static short layoutId(String layoutString) {
        if (StringUtils.isEmpty(layoutString)) {
            return -1;
        }
        Short layoutId = layoutMap.get(layoutString);
        if (layoutId == null) {
            MsgLayout msgLayout = new MsgLayout();
            msgLayout.setLayout(layoutString);
            layoutId = (short) DatabaseManager.saveMsgLayout(msgLayout);
            layoutMap.put(layoutString, layoutId);
        }
        return layoutId;
    }

    /**
     * Insert or update {@link LogFileMeta}.
     *
     * @param meta {@link LogFileMeta}
     */
    public static boolean saveLogFileMeta(LogFileMeta meta) {
        if (meta == null) {
            return false;
        }

        ContentValues values = new ContentValues();
        values.put(LOG_FIELD_LOGGER_NAME, meta.getLoggerName());
        values.put(LOG_FIELD_FILENAME, meta.getFilename());
        values.put(LOG_FIELD_FRAGMENT, meta.getFragment());
        values.put(LOG_FIELD_STATUS, meta.getStatus());
        values.put(LOG_FIELD_MD5, meta.getFileMD5());
        values.put(LOG_FIELD_LOCATION, meta.getLocation());
        values.put(LOG_FIELD_LEVEL, meta.getLevel());
        values.put(LOG_FIELD_EOF, meta.isEof());
        values.put(LOG_FIELD_CREATE_TIME, meta.getCreateTime());
        values.put(LOG_FIELD_DELETE_TIME, meta.getDeleteTime());

        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();
        if (meta.getId() == -1) {
            // 插入当前日期方便查询
            values.put(LOG_FIELD_DATE_STRING, DateTimeUtils.dateString());
            long result = db.insert(TABLE_LOG_FILE_META, null, values);
            return result != -1;
        } else {
            int result = db.update(TABLE_LOG_FILE_META, values, LOG_FIELD_ID + "=" + meta.getId(), null);
            return result > 0;
        }
    }

    public static boolean saveCrashFileMeta(CrashReportFileMeta meta) {
        if (meta == null) {
            return false;
        }

        ContentValues values = new ContentValues();
        values.put(CRASH_FIELD_FILENAME, meta.getFilename());
        values.put(CRASH_FIELD_LOCATION, meta.getLocation());
        values.put(CRASH_FIELD_CREATE_TIME, meta.getCreateTime());
        values.put(CRASH_FIELD_DELETE_TIME, meta.getDeleteTime());
        values.put(CRASH_FIELD_STATUS, meta.getStatus());
        values.put(CRASH_FIELD_FILE_MD5, meta.getFileMD5());

        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();
        if (meta.getId() == -1) {
            long result = db.insert(TABLE_CRASH_REPORT_FILE_META, null, values);
            return result != -1;
        } else {
            int result = db.update(TABLE_CRASH_REPORT_FILE_META, values, LOG_FIELD_ID + "=" + meta.getId(), null);
            return result > 0;
        }
    }

    public static long saveMsgLayout(MsgLayout layout) {
        if (layout == null) {
            return -1;
        }

        ContentValues values = new ContentValues();
        values.put(MSG_LAYOUT_FIELD_LAYOUT, layout.getLayout());

        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();

        long rowId = -1;
        if (layout.getId() == -1) {
            String selection = MSG_LAYOUT_FIELD_LAYOUT + "=?";
            String[] selectionArgs = new String[]{layout.getLayout()};
            Cursor cursor = db.query(TABLE_MSG_LAYOUT, null, selection, selectionArgs, null, null, null, "1");

            if (cursor != null) {
                if (cursor.moveToFirst()) {
                    rowId = cursor.getLong(cursor.getColumnIndex(MSG_LAYOUT_FIELD_ID));
                }
                cursor.close();
            }

            if (rowId == -1) {
                rowId = db.insert(TABLE_MSG_LAYOUT, null, values);
            }
        } else {
            int result = db.update(TABLE_MSG_LAYOUT, values, MSG_LAYOUT_FIELD_ID + "=" + layout.getId(), null);
            if (result > 0) {
                rowId = layout.getId();
            }
        }

        return rowId;
    }

    public static boolean saveAttachmentFileMeta(AttachmentFileMeta meta) {
        if (meta == null) {
            return false;
        }

        ContentValues values = new ContentValues();
        values.put(ATTACHMENT_FIELD_FILENAME, meta.getFilename());
        values.put(ATTACHMENT_FIELD_LOCATION, meta.getLocation());
        values.put(ATTACHMENT_FIELD_SEQUENCE, meta.getSequence());
        values.put(ATTACHMENT_FIELD_CREATE_TIME, meta.getCreateTime());
        values.put(ATTACHMENT_FIELD_DELETE_TIME, meta.getDeleteTime());
        values.put(ATTACHMENT_FIELD_STATUS, meta.getStatus());
        values.put(ATTACHMENT_FIELD_FILE_MD5, meta.getFileMD5());

        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();
        if (meta.getId() == -1) {
            long result = db.insert(TABLE_ATTACHMENT_FILE_META, null, values);
            return result != -1;
        } else {
            int result = db.update(TABLE_ATTACHMENT_FILE_META, values, ATTACHMENT_FIELD_ID + "=" + meta.getId(), null);
            return result > 0;
        }
    }

    /**
     * Find max attachment sequence int number. <br/>
     * <br/>
     * Return -1 when no record exist.
     *
     * @return Sequence number
     */
    public static int findMaxAttachmentSequence() {
        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();

        Cursor cursor =
                db.query(TABLE_ATTACHMENT_FILE_META, null, null, null, null, null, ATTACHMENT_FIELD_SEQUENCE + " DESC",
                        "1");

        int sequence = -1;
        if (cursor != null) {
            if (cursor.moveToFirst()) {
                sequence = cursor.getInt(cursor.getColumnIndex(ATTACHMENT_FIELD_SEQUENCE));
            }
            cursor.close();
        }

        return sequence;
    }

    public static boolean deleteLogFileMeta(LogFileMeta meta) {
        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();
        int result = db.delete(TABLE_LOG_FILE_META, LOG_FIELD_ID + "=" + meta.getId(), null);
        return result > 0;
    }

    public static boolean deleteCrashFileMeta(CrashReportFileMeta meta) {
        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();
        int result = db.delete(TABLE_CRASH_REPORT_FILE_META, LOG_FIELD_ID + "=" + meta.getId(), null);
        return result > 0;
    }

    public static boolean deleteMsgLayout(MsgLayout layout) {
        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();
        int result = db.delete(TABLE_MSG_LAYOUT, MSG_LAYOUT_FIELD_ID + "=" + layout.getId(), null);
        return result > 0;
    }

    private static LogFileMeta findByFilename(String filename) {
        String selection = LOG_FIELD_FILENAME + "=?";
        String[] selectionArgs = new String[]{filename};

        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();

        Cursor cursor = db.query(TABLE_LOG_FILE_META, null, selection, selectionArgs, null, null, null, "1");
        LogFileMeta meta = null;
        if (cursor != null) {
            if (cursor.moveToFirst()) {
                meta = manager.getLogFileMeta(cursor);
            }
            cursor.close();
        }
        return meta;
    }

    public static boolean closeLogFile(String loggerName, int level, int fragment) {
        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();

        String date = DateTimeUtils.dateString();

        ContentValues values = new ContentValues();
        values.put(LOG_FIELD_EOF, true);
        values.put(LOG_FIELD_STATUS, LoggerConstants.STATE_WILL_UPLOAD);

        String[] conditions =
                new String[]{String.format("%s='%s'", LOG_FIELD_LOGGER_NAME, loggerName),
                        String.format("%s=%d", LOG_FIELD_LEVEL, level),
                        String.format("%s=%d", LOG_FIELD_FRAGMENT, fragment),
                        String.format("%s=%s", LOG_FIELD_DATE_STRING, date)};

        int result = db.update(TABLE_LOG_FILE_META, values, StringUtils.join(conditions, " AND "), null);
        return result > 0;
    }

    /**
     * 关闭所有正在写入的日志文件.
     *
     * @return result
     */
    public static boolean closeAllFile() {
        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();

        ContentValues values = new ContentValues();
        values.put(LOG_FIELD_EOF, true);
        values.put(LOG_FIELD_STATUS, LoggerConstants.STATE_WILL_UPLOAD);
        int result = db.update(TABLE_LOG_FILE_META, values, LOG_FIELD_EOF + "=0", null);

        return result > 0;
    }

    public static LogFileMeta findMaxFragment(LogEvent logEvent) {
        if (logEvent == null) {
            return null;
        }
        String loggerName = logEvent.getLoggerName();
        int level = logEvent.getLevel();
        String date = DateTimeUtils.dateString();
        String selection =
                LOG_FIELD_LOGGER_NAME + "=?" + AND + LOG_FIELD_LEVEL + "=?" + AND + LOG_FIELD_DATE_STRING + "=?";
        String[] selectionArgs = new String[]{loggerName, String.valueOf(level), date};

        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();
        Cursor cursor =
                db.query(TABLE_LOG_FILE_META, null, selection, selectionArgs, null, null, LOG_FIELD_FRAGMENT + " DESC",
                        "1");
        LogFileMeta meta = null;
        if (cursor != null) {
            if (cursor.moveToFirst()) {
                meta = manager.getLogFileMeta(cursor);
            }
            cursor.close();
        }
        return meta;
    }

    /**
     * 查找指定 level 的日志文件记录.
     *
     * @param levels 日志 levels
     * @param status 文件状态
     * @return {@link LogFileMeta} list
     */
    public static List<LogFileMeta> findAllLogFileMetaByLevel(int[] levels, int status) {
        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();
        int size = levels.length;
        if (size > 0) {
            String[] selectionArray = new String[size];
            String[] selectionArgs = new String[size];
            for (int i = 0; i < size; i++) {
                selectionArray[i] = LOG_FIELD_LEVEL + "=?";
                selectionArgs[i] = String.valueOf(levels[i]);
            }
            String selection = StringUtils.join(selectionArray, " OR ");

            if (status != LoggerConstants.STATE_ALL) {
                selection = String.format("(%s) AND %s = %d", selection, LOG_FIELD_STATUS, status);
            }

            Cursor cursor = db.query(TABLE_LOG_FILE_META, null, selection, selectionArgs, null, null, null, null);
            return manager.exactLogFileMetaCursor(cursor);
        }

        return new ArrayList<LogFileMeta>();
    }

    /**
     * 查找指定 level 和时间间隔内创建的日志文件记录.
     *
     * @param levels    日志 levels
     * @param startTime 起始时间
     * @param endTime   结束时间
     * @param status    文件状态
     * @return {@link LogFileMeta} list
     */
    public static List<LogFileMeta> findAllLogFileMetaByLevelAndTime(int[] levels, long startTime, long endTime,
                                                                     int status) {
        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();

        int length = levels.length;
        if (length > 0) {
            String[] selectionArray = new String[length];
            String[] selectionArgs = new String[length + 2];
            for (int i = 0; i < length; i++) {
                selectionArray[i] = LOG_FIELD_LEVEL + "=?";
                selectionArgs[i] = String.valueOf(levels[i]);
            }
            String selection =
                    StringUtils.join(selectionArray, " OR ") + AND + LOG_FIELD_CREATE_TIME + ">=?" + AND
                            + LOG_FIELD_CREATE_TIME + "<=?";
            selectionArgs[length] = String.valueOf(startTime);
            selectionArgs[length + 1] = String.valueOf(endTime);

            if (status != LoggerConstants.STATE_ALL) {
                selection = String.format("(%s) AND %s = %d", selection, LOG_FIELD_STATUS, status);
            }

            Cursor cursor = db.query(TABLE_LOG_FILE_META, null, selection, selectionArgs, null, null, null, null);
            return manager.exactLogFileMetaCursor(cursor);
        }

        return new ArrayList<LogFileMeta>();
    }

    /**
     * 查找指定时间间隔内创建的日志文件记录.
     *
     * @param startTime 起始时间
     * @param endTime   结束时间
     * @param status    文件状态
     * @return {@link LogFileMeta} list
     */
    public static List<LogFileMeta> findAllLogFileMetaByTime(long startTime, long endTime, int status) {
        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();

        String selection = LOG_FIELD_CREATE_TIME + ">=?" + AND + LOG_FIELD_CREATE_TIME + "<=?";
        String[] selectionArgs = new String[]{String.valueOf(startTime), String.valueOf(endTime)};

        if (status != LoggerConstants.STATE_ALL) {
            selection = String.format("(%s) AND %s = %d", selection, LOG_FIELD_STATUS, status);
        }

        Cursor cursor = db.query(TABLE_LOG_FILE_META, null, selection, selectionArgs, null, null, null, null);

        return manager.exactLogFileMetaCursor(cursor);
    }

    /**
     * 读取所有的日志文件记录.
     *
     * @return {@link LogFileMeta} list
     */
    public static List<LogFileMeta> findAllLogFileMeta() {
        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();

        Cursor cursor = db.query(TABLE_LOG_FILE_META, null, null, null, null, null, null, null);
        return manager.exactLogFileMetaCursor(cursor);
    }

    /**
     * 读取所有的崩溃日志文件记录.
     *
     * @param status 文件状态
     * @return {@link CrashReportFileMeta} list
     */
    public static List<CrashReportFileMeta> findAllCrashFileMeta(int status) {
        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();

        String selection = null;
        if (status != LoggerConstants.STATE_ALL) {
            selection = String.format("%s = %d", CRASH_FIELD_STATUS, status);
        }

        Cursor cursor = db.query(TABLE_CRASH_REPORT_FILE_META, null, selection, null, null, null, null, null);
        return manager.exactCrashFileMetaCursor(cursor);
    }

    /**
     * 读取所有的附件文件记录.
     *
     * @param status 文件状态
     * @return {@link AttachmentFileMeta} list
     */
    public static List<AttachmentFileMeta> findAllAttachmentFileMeta(int status) {
        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();

        String selection = null;
        if (status != LoggerConstants.STATE_ALL) {
            selection = String.format("%s = %d", ATTACHMENT_FIELD_STATUS, status);
        }

        Cursor cursor = db.query(TABLE_ATTACHMENT_FILE_META, null, selection, null, null, null, null, null);
        return manager.exactAttachmentFileMetaCursor(cursor);
    }

    /**
     * 读取所有 Msg layout 记录.
     *
     * @return {@link MsgLayout} list
     */
    public static List<MsgLayout> getMsgLayoutList() {
        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();

        Cursor cursor = db.query(TABLE_MSG_LAYOUT, null, null, null, null, null, null, null);
        return manager.exactMsgLayoutCursor(cursor);
    }

    public static String getLayoutJson() {
        DatabaseManager manager = DatabaseManager.manager();
        SQLiteDatabase db = manager.getWritableDatabase();

        JSONArray jsonArray = new JSONArray();
        Cursor cursor = db.query(TABLE_MSG_LAYOUT, null, null, null, null, null, null, null);
        if (cursor.moveToFirst()) {
            while (!cursor.isAfterLast()) {
                try {
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put(MSG_LAYOUT_FIELD_ID, cursor.getLong(cursor.getColumnIndex(MSG_LAYOUT_FIELD_ID)));
                    jsonObject.put(MSG_LAYOUT_FIELD_LAYOUT,
                            cursor.getString(cursor.getColumnIndex(MSG_LAYOUT_FIELD_LAYOUT)));
                    jsonArray.put(jsonObject);
                } catch (JSONException e) {
                    LogUtil.e("DatabaseManager", "JSONException", e);
                }

                cursor.moveToNext();
            }
        }
        cursor.close();

        return jsonArray.toString();
    }

    private List<LogFileMeta> exactLogFileMetaCursor(Cursor cursor) {
        List<LogFileMeta> metaList = new ArrayList<LogFileMeta>();
        if (cursor == null) {
            return metaList;
        }
        if (cursor.moveToFirst()) {
            while (!cursor.isAfterLast()) {
                LogFileMeta meta = getLogFileMeta(cursor);
                metaList.add(meta);
                cursor.moveToNext();
            }
        }
        cursor.close();

        return metaList;
    }

    private LogFileMeta getLogFileMeta(Cursor cursor) {
        LogFileMeta meta = new LogFileMeta();
        meta.setId(cursor.getLong(cursor.getColumnIndex(LOG_FIELD_ID)));
        meta.setLoggerName(cursor.getString(cursor.getColumnIndex(LOG_FIELD_LOGGER_NAME)));
        meta.setFilename(cursor.getString(cursor.getColumnIndex(LOG_FIELD_FILENAME)));
        meta.setFragment(cursor.getInt(cursor.getColumnIndex(LOG_FIELD_FRAGMENT)));
        meta.setStatus(cursor.getInt(cursor.getColumnIndex(LOG_FIELD_STATUS)));
        meta.setFileMD5(cursor.getString(cursor.getColumnIndex(LOG_FIELD_MD5)));
        meta.setLocation(cursor.getInt(cursor.getColumnIndex(LOG_FIELD_LOCATION)));
        meta.setLevel(cursor.getInt(cursor.getColumnIndex(LOG_FIELD_LEVEL)));
        int eofValue = cursor.getInt(cursor.getColumnIndex(LOG_FIELD_EOF));
        meta.setEof(eofValue != 0);
        meta.setCreateTime(cursor.getLong(cursor.getColumnIndex(LOG_FIELD_CREATE_TIME)));
        meta.setDeleteTime(cursor.getLong(cursor.getColumnIndex(LOG_FIELD_DELETE_TIME)));
        return meta;
    }

    private List<CrashReportFileMeta> exactCrashFileMetaCursor(Cursor cursor) {
        List<CrashReportFileMeta> metaList = new ArrayList<CrashReportFileMeta>();
        if (cursor == null) {
            return metaList;
        }
        if (cursor.moveToFirst()) {
            while (!cursor.isAfterLast()) {
                CrashReportFileMeta meta = getCrashFileMeta(cursor);
                metaList.add(meta);
                cursor.moveToNext();
            }
        }
        cursor.close();

        return metaList;
    }

    private CrashReportFileMeta getCrashFileMeta(Cursor cursor) {
        CrashReportFileMeta meta = new CrashReportFileMeta();
        meta.setId(cursor.getLong(cursor.getColumnIndex(CRASH_FIELD_ID)));
        meta.setFilename(cursor.getString(cursor.getColumnIndex(CRASH_FIELD_FILENAME)));
        meta.setLocation(cursor.getInt(cursor.getColumnIndex(CRASH_FIELD_LOCATION)));
        meta.setCreateTime(cursor.getLong(cursor.getColumnIndex(CRASH_FIELD_CREATE_TIME)));
        meta.setDeleteTime(cursor.getLong(cursor.getColumnIndex(CRASH_FIELD_DELETE_TIME)));
        meta.setStatus(cursor.getInt(cursor.getColumnIndex(CRASH_FIELD_STATUS)));
        meta.setFileMD5(cursor.getString(cursor.getColumnIndex(CRASH_FIELD_FILE_MD5)));
        return meta;
    }

    private List<MsgLayout> exactMsgLayoutCursor(Cursor cursor) {
        List<MsgLayout> layoutList = new ArrayList<MsgLayout>();
        if (cursor == null) {
            return layoutList;
        }
        if (cursor.moveToFirst()) {
            while (!cursor.isAfterLast()) {
                MsgLayout layout = getMsgLayout(cursor);
                layoutList.add(layout);
                cursor.moveToNext();
            }
        }
        cursor.close();

        return layoutList;
    }

    private MsgLayout getMsgLayout(Cursor cursor) {
        MsgLayout layout = new MsgLayout();
        layout.setId(cursor.getLong(cursor.getColumnIndex(MSG_LAYOUT_FIELD_ID)));
        layout.setLayout(cursor.getString(cursor.getColumnIndex(MSG_LAYOUT_FIELD_LAYOUT)));
        return layout;
    }

    private List<AttachmentFileMeta> exactAttachmentFileMetaCursor(Cursor cursor) {
        List<AttachmentFileMeta> metaList = new ArrayList<AttachmentFileMeta>();
        if (cursor == null) {
            return metaList;
        }
        if (cursor.moveToFirst()) {
            while (!cursor.isAfterLast()) {
                AttachmentFileMeta meta = getAttachmentFileMeta(cursor);
                metaList.add(meta);
                cursor.moveToNext();
            }
        }
        cursor.close();

        return metaList;
    }

    private AttachmentFileMeta getAttachmentFileMeta(Cursor cursor) {
        AttachmentFileMeta meta = new AttachmentFileMeta();
        meta.setId(cursor.getLong(cursor.getColumnIndex(ATTACHMENT_FIELD_ID)));
        meta.setFilename(cursor.getString(cursor.getColumnIndex(ATTACHMENT_FIELD_FILENAME)));
        meta.setLocation(cursor.getInt(cursor.getColumnIndex(ATTACHMENT_FIELD_LOCATION)));
        meta.setSequence(cursor.getInt(cursor.getColumnIndex(ATTACHMENT_FIELD_SEQUENCE)));
        meta.setCreateTime(cursor.getLong(cursor.getColumnIndex(ATTACHMENT_FIELD_CREATE_TIME)));
        meta.setDeleteTime(cursor.getLong(cursor.getColumnIndex(ATTACHMENT_FIELD_DELETE_TIME)));
        meta.setStatus(cursor.getInt(cursor.getColumnIndex(ATTACHMENT_FIELD_STATUS)));
        meta.setFileMD5(cursor.getString(cursor.getColumnIndex(ATTACHMENT_FIELD_FILE_MD5)));
        return meta;
    }
} public class CrashReportFileMeta {

    private long id;

    private String filename;

    private int location;

    private long createTime;

    private long deleteTime;

    private int status;

    private String fileMD5;

    public CrashReportFileMeta() {
        this.id = -1;
        this.status = LoggerConstants.STATE_NORMAL;
        this.createTime = System.currentTimeMillis();
        this.location = LoggerConstants.LOCATION_EXTERNAL;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getFileMD5() {
        return fileMD5;
    }

    public void setFileMD5(String fileMD5) {
        this.fileMD5 = fileMD5;
    }

    public String getFilename() {
        return filename;
    }

    public void setFilename(String filename) {
        this.filename = filename;
    }

    public int getLocation() {
        return location;
    }

    public void setLocation(int location) {
        this.location = location;
    }

    public long getCreateTime() {
        return createTime;
    }

    public void setCreateTime(long createTime) {
        this.createTime = createTime;
    }

    public long getDeleteTime() {
        return deleteTime;
    }

    public void setDeleteTime(long deleteTime) {
        this.deleteTime = deleteTime;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

} public class Compatibility {

    public static int getAPILevel() {
        int apiLevel;
        try {
            // This field has been added in Android 1.6
            final Field SDK_INT = Build.VERSION.class.getField("SDK_INT");
            apiLevel = SDK_INT.getInt(null);
        } catch (SecurityException e) {
            apiLevel = Integer.parseInt(Build.VERSION.SDK);
        } catch (NoSuchFieldException e) {
            apiLevel = Integer.parseInt(Build.VERSION.SDK);
        } catch (IllegalArgumentException e) {
            apiLevel = Integer.parseInt(Build.VERSION.SDK);
        } catch (IllegalAccessException e) {
            apiLevel = Integer.parseInt(Build.VERSION.SDK);
        }

        return apiLevel;
    }
} public class EnvironmentUtils {

    /**
     * Calculates the free memory of the device. This is based on an inspection of the filesystem,
     * which in android devices is stored in RAM.
     *
     * @return Number of bytes available.
     */
    public static long getAvailableInternalMemorySize() {
        final File path = Environment.getDataDirectory();
        final StatFs stat = new StatFs(path.getPath());
        return getAvailableMemorySize(stat);
    }

    /**
     * Calculates the total memory of the device. This is based on an inspection of the filesystem,
     * which in android devices is stored in RAM.
     *
     * @return Total number of bytes.
     */
    public static long getTotalInternalMemorySize() {
        final File path = Environment.getDataDirectory();
        final StatFs stat = new StatFs(path.getPath());
        return getTotalMemorySize(stat);
    }

    public static long getAvailableExternalMemorySize() {
        File path = Environment.getExternalStorageDirectory();
        StatFs stat = new StatFs(path.getPath());
        return getAvailableMemorySize(stat);
    }

    public static long getTotalExternalMemorySize() {
        File path = Environment.getExternalStorageDirectory();
        StatFs stat = new StatFs(path.getPath());
        return getTotalMemorySize(stat);
    }

    @TargetApi(value = Build.VERSION_CODES.JELLY_BEAN_MR2)
    public static long getTotalMemorySize(final StatFs stat) {
        if (Compatibility.getAPILevel() >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            final long blockSize = stat.getBlockSizeLong();
            final long totalBlocks = stat.getBlockCountLong();
            return totalBlocks * blockSize;

        } else {
            final long blockSize = stat.getBlockSize();
            final long totalBlocks = stat.getBlockCount();
            return totalBlocks * blockSize;
        }
    }

    @TargetApi(value = Build.VERSION_CODES.JELLY_BEAN_MR2)
    public static long getAvailableMemorySize(final StatFs stat) {
        if (Compatibility.getAPILevel() >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            final long blockSize = stat.getBlockSizeLong();
            final long availableBlocks = stat.getAvailableBlocksLong();
            return availableBlocks * blockSize;
        } else {
            final long blockSize = stat.getBlockSize();
            final long availableBlocks = stat.getAvailableBlocks();
            return availableBlocks * blockSize;
        }
    }
} public class LogStorage {

    public static boolean readable() {
        String state = Environment.getExternalStorageState();
        return Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
    }

    public static boolean writable() {
        String state = Environment.getExternalStorageState();
        return Environment.MEDIA_MOUNTED.equals(state);
    }

    public static String externalLogDir() {
        return externalDir(LoggerConstants.LOG_DIR_NAME);
    }

    public static String internalLogDir() {
        return internalDir(LoggerConstants.LOG_DIR_NAME);
    }

    public static String externalCrashReportDir() {
        return externalDir(LoggerConstants.CRASH_REPORT_DIR_NAME);
    }

    public static String internalCrashReportDir() {
        return internalDir(LoggerConstants.CRASH_REPORT_DIR_NAME);
    }

    public static String externalAttachmentDir() {
        return externalDir(LoggerConstants.ATTACHMENT_DIR_NAME);
    }

    public static String internalAttachmentDir() {
        return internalDir(LoggerConstants.ATTACHMENT_DIR_NAME);
    }

    public static String writableLogFilePath(LogFileMeta meta) {
        return logFilePath(meta, true);
    }

    public static String readableLogFilePath(LogFileMeta meta) {
        return logFilePath(meta, false);
    }

    public static String writableCrashReportFilePath(CrashReportFileMeta meta) {
        return crashReportFilePath(meta, true);
    }

    public static String readableCrashReportFilePath(CrashReportFileMeta meta) {
        return crashReportFilePath(meta, false);
    }

    public static String writableAttachmentFilePath(AttachmentFileMeta meta) {
        return attachmentFilePath(meta, true);
    }

    public static String readableAttachmentFilePath(AttachmentFileMeta meta) {
        return attachmentFilePath(meta, false);
    }

    private static String logFilePath(LogFileMeta meta, boolean writable) {
        if (meta == null) {
            return null;
        }
        return fileFullPath(LoggerConstants.LOG_DIR_NAME, writable, meta.getLocation(), meta.getFilename());
    }

    public static String crashReportFilePath(CrashReportFileMeta meta, boolean writable) {
        if (meta == null) {
            return null;
        }
        return fileFullPath(LoggerConstants.CRASH_REPORT_DIR_NAME, writable, meta.getLocation(), meta.getFilename());
    }

    public static String attachmentFilePath(AttachmentFileMeta meta, boolean writable) {
        if (meta == null) {
            return null;
        }
        return fileFullPath(LoggerConstants.ATTACHMENT_DIR_NAME, writable, meta.getLocation(), meta.getFilename());
    }

    public static File configFile() {
        Context context = LoggerFactory.context();
        if (context == null) {
            return null;
        }
        if (LogStorage.readable()) {
            return new File(context.getExternalFilesDir(null), LoggerConstants.CONFIG_FILE_NAME);
        } else {
            return new File(context.getFilesDir(), LoggerConstants.CONFIG_FILE_NAME);
        }
    }

    private static String fileFullPath(String dirName, boolean writable, int location, String filename) {
        String dirPath = null;
        switch (location) {
            case LoggerConstants.LOCATION_EXTERNAL:
                if (writable && writable()) {
                    dirPath = externalDir(dirName);
                } else if (!writable && readable()) {
                    dirPath = externalDir(dirName);
                }
                break;
            case LoggerConstants.LOCATION_INTERNAL:
                dirPath = internalDir(dirName);
                break;
            default:
                break;
        }
        if (dirPath != null) {
            return dirPath + "/" + filename;
        }
        return null;
    }

    public static String externalDir(String dirName) {
        Context context = LoggerFactory.context();
        if (context == null) {
            return null;
        }

        File dir = new File(context.getExternalFilesDir(null), dirName);
        if (dir.exists()) {
            if (dir.isDirectory()) {
                return dir.getAbsolutePath();
            } else {
                if (dir.delete()) {
                    return dir.mkdirs() ? dir.getAbsolutePath() : null;
                }
            }
        } else {
            return dir.mkdirs() ? dir.getAbsolutePath() : null;
        }

        return null;
    }

    public static String internalDir(String dirName) {
        Context context = LoggerFactory.context();
        if (context == null) {
            return null;
        }

        File dir = new File(context.getFilesDir(), dirName);
        if (dir.exists()) {
            if (dir.isDirectory()) {
                return dir.getAbsolutePath();
            } else {
                if (dir.delete()) {
                    return dir.mkdirs() ? dir.getAbsolutePath() : null;
                }
            }
        } else {
            return dir.mkdirs() ? dir.getAbsolutePath() : null;
        }
        return null;
    }

} public class LogUtil {

    public static void d(String tag, String msg) {
        if (LoggerFactory.isDebug()) {
            Log.d(tag, msg);
        }
    }

    public static void d(String tag, String msg, Throwable tr) {
        if (LoggerFactory.isDebug()) {
            Log.d(tag, msg, tr);
        }
    }

    public static void e(String tag, String msg) {
        if (LoggerFactory.isDebug()) {
            Log.e(tag, msg);
        }
    }

    public static void e(String tag, String msg, Throwable tr) {
        if (LoggerFactory.isDebug()) {
            Log.e(tag, msg, tr);
        }
    }

    public static void i(String tag, String msg) {
        if (LoggerFactory.isDebug()) {
            Log.i(tag, msg);
        }
    }

    public static void i(String tag, String msg, Throwable tr) {
        if (LoggerFactory.isDebug()) {
            Log.i(tag, msg, tr);
        }
    }

    public static void v(String tag, String msg, Throwable tr) {
        if (LoggerFactory.isDebug()) {
            Log.v(tag, msg, tr);
        }
    }

    public static void v(String tag, String msg) {
        if (LoggerFactory.isDebug()) {
            Log.v(tag, msg);
        }
    }

    public static void w(String tag, Throwable tr) {
        if (LoggerFactory.isDebug()) {
            Log.w(tag, tr);
        }
    }

    public static void w(String tag, String msg, Throwable tr) {
        if (LoggerFactory.isDebug()) {
            Log.w(tag, msg, tr);
        }
    }

    public static void w(String tag, String msg) {
        if (LoggerFactory.isDebug()) {
            Log.w(tag, msg);
        }
    }

    public static void wtf(String tag, Throwable tr) {
        if (LoggerFactory.isDebug()) {
            Log.wtf(tag, tr);
        }
    }

    public static void wtf(String tag, String msg, Throwable tr) {
        if (LoggerFactory.isDebug()) {
            Log.wtf(tag, msg, tr);
        }
    }

    public static void wtf(String tag, String msg) {
        if (LoggerFactory.isDebug()) {
            Log.wtf(tag, msg);
        }
    }
} public class PackageManagerWrapper {
    private final Context context;

    public PackageManagerWrapper(Context context) {
        this.context = context;
    }

    /**
     * @param permission Manifest.permission to check whether it has been granted.
     * @return true if the permission has been granted to the app, false if it hasn't been granted
     *         or the PackageManager could not be contacted.
     */
    public boolean hasPermission(String permission) {
        final PackageManager pm = context.getPackageManager();
        if (pm == null) {
            return false;
        }

        try {
            return pm.checkPermission(permission, context.getPackageName()) == PackageManager.PERMISSION_GRANTED;
        } catch (RuntimeException e) {
            // To catch RuntimeException("Package manager has died") that can occur on some version
            // of Android,
            // when the remote PackageManager is unavailable. I suspect this sometimes occurs when
            // the App is being reinstalled.
            return false;
        }
    }

    /**
     * @return PackageInfo for the current application or null if the PackageManager could not be
     *         contacted.
     */
    public PackageInfo getPackageInfo() {
        final PackageManager pm = context.getPackageManager();
        if (pm == null) {
            return null;
        }

        try {
            return pm.getPackageInfo(context.getPackageName(), 0);
        } catch (NameNotFoundException e) {
            LogUtil.e("PackageManagerWrapper", "NameNotFoundException", e);
            return null;
        } catch (RuntimeException e) {
            // To catch RuntimeException("Package manager has died") that can occur on some version
            // of Android,
            // when the remote PackageManager is unavailable. I suspect this sometimes occurs when
            // the App is being reinstalled.
            return null;
        }
    }
} public class SystemInfo {

    private static final String TAG = "SystemInfo";

    private static String packageName;
    private static int versionCode = -1;
    private static String versionName;

    public static String appId() {
        if (!StringUtils.isEmpty(packageName)) {
            return packageName;
        }

        readPackageInfo();

        if (StringUtils.isEmpty(packageName)) {
            return "";
        } else {
            return packageName;
        }
    }

    public static int version() {
        if (versionCode != -1) {
            return versionCode;
        }

        readPackageInfo();

        return versionCode;
    }

    public static String versionString() {
        if (!StringUtils.isEmpty(versionName)) {
            return versionName;
        }

        readPackageInfo();

        if (StringUtils.isEmpty(versionName)) {
            return "";
        } else {
            return versionName;
        }
    }

    public static String imei() {
        Context context = LoggerFactory.context();
        if (context == null) {
            return "";
        }
        PackageManagerWrapper pm = new PackageManagerWrapper(context);
        if (pm.hasPermission(Manifest.permission.READ_PHONE_STATE)) {
            TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);

            String deviceId = telephonyManager.getDeviceId();
            if (deviceId != null && deviceId.length() > 0 && !deviceId.contains("*")
                    && countZero(deviceId) != deviceId.length()) {
                return deviceId;
            }
        }
        return "";
    }

    public static String macAddress() {
        Context context = LoggerFactory.context();
        if (context == null) {
            return "";
        }
        PackageManagerWrapper pm = new PackageManagerWrapper(context);
        if (pm.hasPermission(Manifest.permission.ACCESS_WIFI_STATE)
                && pm.hasPermission(Manifest.permission.ACCESS_NETWORK_STATE)) {
            WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
            if (wifiManager.isWifiEnabled()) {
                WifiInfo info = wifiManager.getConnectionInfo();
                return info.getMacAddress();
            }
        }
        return "";
    }

    private static void readPackageInfo() {
        Context context = LoggerFactory.context();
        if (context == null) {
            return;
        }

        try {
            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            packageName = packageInfo.packageName;
            versionCode = packageInfo.versionCode;
            versionName = packageInfo.versionName;
        } catch (PackageManager.NameNotFoundException e) {
            LogUtil.e(TAG, "", e);
        }
    }

    private static int countZero(String string) {
        int count = 0;
        for (int i = 0; i < string.length(); i++) {
            if (string.charAt(i) == '0') {
                count++;
            }
        }
        return count;
    }

}
import android.Manifest;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.util.JsonWriter;
import com.getui.logful.LoggerConstants;
import com.getui.logful.LoggerFactory;
import com.getui.logful.db.DatabaseManager;
import com.getui.logful.entity.CrashReportFileMeta;
import com.getui.logful.util.Compatibility;
import com.getui.logful.util.EnvironmentUtils;
import com.getui.logful.util.LogStorage;
import com.getui.logful.util.LogUtil;
import com.getui.logful.util.PackageManagerWrapper;
import com.getui.logful.util.SystemInfo;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.util.UUID;
package com.getui.logful.crash;




public class CrashReportWriter {

    public static void write(Thread crashThread, Throwable throwable) throws Exception {
        Context context = LoggerFactory.context();
        if (context == null) {
            return;
        }

        int location;
        String crashReportDir;
        if (LogStorage.writable()) {