From 786d46069ce1c52225f71a7b6861c31985a400ac Mon Sep 17 00:00:00 2001 From: Fankesyooni Date: Fri, 13 May 2022 02:27:57 +0800 Subject: [PATCH] Added export all errors record function --- .../apperrorstracking/locale/LocaleString.kt | 12 ++ .../ui/activity/AppErrorsRecordActivity.kt | 65 +++++++- .../utils/tool/ZipFileTool.kt | 146 ++++++++++++++++++ app/src/main/res/values-ja/strings.xml | 2 + app/src/main/res/values-zh-rCN/strings.xml | 2 + app/src/main/res/values-zh-rHK/strings.xml | 2 + app/src/main/res/values-zh-rMO/strings.xml | 2 + app/src/main/res/values-zh-rTW/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 9 files changed, 230 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/com/fankes/apperrorstracking/utils/tool/ZipFileTool.kt diff --git a/app/src/main/java/com/fankes/apperrorstracking/locale/LocaleString.kt b/app/src/main/java/com/fankes/apperrorstracking/locale/LocaleString.kt index d0f6d70..25c3463 100644 --- a/app/src/main/java/com/fankes/apperrorstracking/locale/LocaleString.kt +++ b/app/src/main/java/com/fankes/apperrorstracking/locale/LocaleString.kt @@ -202,4 +202,16 @@ object LocaleString { /** @string Automatic generated */ fun areYouSureExportAllErrors(vararg objArrs: Any) = R.string.are_you_sure_export_all_errors.bind(*objArrs) + + /** @string Automatic generated */ + val exportAllErrorsSuccess get() = exportAllErrorsSuccess() + + /** @string Automatic generated */ + fun exportAllErrorsSuccess(vararg objArrs: Any) = R.string.export_all_errors_success.bind(*objArrs) + + /** @string Automatic generated */ + val exportAllErrorsFail get() = exportAllErrorsFail() + + /** @string Automatic generated */ + fun exportAllErrorsFail(vararg objArrs: Any) = R.string.export_all_errors_fail.bind(*objArrs) } \ No newline at end of file diff --git a/app/src/main/java/com/fankes/apperrorstracking/ui/activity/AppErrorsRecordActivity.kt b/app/src/main/java/com/fankes/apperrorstracking/ui/activity/AppErrorsRecordActivity.kt index da35182..6384f75 100644 --- a/app/src/main/java/com/fankes/apperrorstracking/ui/activity/AppErrorsRecordActivity.kt +++ b/app/src/main/java/com/fankes/apperrorstracking/ui/activity/AppErrorsRecordActivity.kt @@ -19,8 +19,12 @@ * * This file is Created by fankes on 2022/5/11. */ +@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION") + package com.fankes.apperrorstracking.ui.activity +import android.app.Activity +import android.content.Intent import android.view.* import android.widget.AdapterView.AdapterContextMenuInfo import android.widget.BaseAdapter @@ -33,11 +37,21 @@ import com.fankes.apperrorstracking.locale.LocaleString import com.fankes.apperrorstracking.ui.activity.base.BaseActivity import com.fankes.apperrorstracking.utils.factory.* import com.fankes.apperrorstracking.utils.tool.FrameworkTool -import java.text.SimpleDateFormat -import java.util.* +import com.fankes.apperrorstracking.utils.tool.ZipFileTool +import java.io.File +import java.io.FileInputStream class AppErrorsRecordActivity : BaseActivity() { + companion object { + + /** 请求保存文件回调标识 */ + private const val WRITE_REQUEST_CODE = 0 + } + + /** 当前导出文件的路径 */ + private var outPutFilePath = "" + /** 回调适配器改变 */ private var onChanged: (() -> Unit)? = null @@ -60,8 +74,12 @@ class AppErrorsRecordActivity : BaseActivity() { } } binding.exportAllIcon.setOnClickListener { - // TODO 待实现 - toast(msg = "Coming soon") + showDialog { + title = LocaleString.notice + msg = LocaleString.areYouSureExportAllErrors + confirmButton { exportAll() } + cancelButton() + } } /** 设置列表元素和 Adapter */ binding.listView.apply { @@ -84,7 +102,7 @@ class AppErrorsRecordActivity : BaseActivity() { getItem(position).also { holder.appIcon.setImageDrawable(appIcon(it.packageName)) holder.appNameText.text = appName(it.packageName) - holder.errorsTimeText.text = SimpleDateFormat.getDateTimeInstance().format(Date(it.timestamp)) + holder.errorsTimeText.text = it.time holder.errorTypeIcon.setImageResource(if (it.isNativeCrash) R.drawable.ic_cpp else R.drawable.ic_java) holder.errorTypeText.text = if (it.isNativeCrash) "Native crash" else it.exceptionClassName.let { text -> if (text.contains(other = ".")) text.split(".").let { e -> e[e.lastIndex] } else text @@ -112,6 +130,32 @@ class AppErrorsRecordActivity : BaseActivity() { } } + /** 打包导出全部 */ + private fun exportAll() { + clearAllExportTemp() + ("${cacheDir.absolutePath}/temp").also { path -> + File(path).mkdirs() + listData.takeIf { it.isNotEmpty() }?.forEach { + File("$path/${it.packageName}_${it.timestamp}.log").writeText(it.stackOutputContent) + } + outPutFilePath = "${cacheDir.absolutePath}/temp_${System.currentTimeMillis()}.zip" + ZipFileTool.zipMultiFile(path, outPutFilePath) + runCatching { + startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "*/application" + putExtra(Intent.EXTRA_TITLE, "app_errors_info_${System.currentTimeMillis()}.zip") + }, WRITE_REQUEST_CODE) + }.onFailure { toast(msg = "Start Android SAF failed") } + } + } + + /** 清空导出的临时文件 */ + private fun clearAllExportTemp() { + cacheDir.deleteRecursively() + cacheDir.mkdirs() + } + override fun onCreateContextMenu(menu: ContextMenu?, v: View?, menuInfo: ContextMenu.ContextMenuInfo?) { menuInflater.inflate(R.menu.menu_list_detail_action, menu) super.onCreateContextMenu(menu, v, menuInfo) @@ -128,6 +172,17 @@ class AppErrorsRecordActivity : BaseActivity() { return super.onContextItemSelected(item) } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) runCatching { + data?.data?.let { + contentResolver?.openOutputStream(it)?.apply { write(FileInputStream(outPutFilePath).readBytes()) }?.close() + clearAllExportTemp() + toast(LocaleString.exportAllErrorsSuccess) + } ?: toast(LocaleString.exportAllErrorsFail) + }.onFailure { toast(LocaleString.exportAllErrorsFail) } + } + override fun onResume() { super.onResume() /** 执行更新 */ diff --git a/app/src/main/java/com/fankes/apperrorstracking/utils/tool/ZipFileTool.kt b/app/src/main/java/com/fankes/apperrorstracking/utils/tool/ZipFileTool.kt new file mode 100644 index 0000000..e03cdd9 --- /dev/null +++ b/app/src/main/java/com/fankes/apperrorstracking/utils/tool/ZipFileTool.kt @@ -0,0 +1,146 @@ +/* + * AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/KitsunePie/AppErrorsTracking + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/5/13. + */ +@file:Suppress("unused") + +package com.fankes.apperrorstracking.utils.tool + +import java.io.* +import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream +import java.util.zip.ZipOutputStream + +/** + * 处理压缩文件 + */ +object ZipFileTool { + + private const val BUFF_SIZE = 2048 + + /** + * 压缩整个文件夹中的所有文件 - 生成指定名称的 Zip 压缩包 + * @param filePath 文件所在目录 + * @param zipPath 压缩后 Zip 文件名称 + * @param isDirFlag Zip 文件中第一层是否包含一级目录 - true 包含 false没有 + */ + fun zipMultiFile(filePath: String, zipPath: String, isDirFlag: Boolean = false) { + runCatching { + val file = File(filePath) + val zipFile = File(zipPath) + val zipOut = ZipOutputStream(FileOutputStream(zipFile)) + if (file.isDirectory) { + val files = file.listFiles() ?: emptyArray() + for (fileSec in files) + if (isDirFlag) recursionZip(zipOut, fileSec, file.name + File.separator) + else recursionZip(zipOut, fileSec, "") + } + zipOut.close() + } + } + + /** + * 解压文件 + * @param unZipPath 解压后的目录 + * @param zipPath 压缩文件目录 + */ + fun unZipFile(unZipPath: String, zipPath: String) { + runCatching { + unZipFileByInput(unZipPath, FileInputStream(zipPath)) + } + } + + /** + * 解压文件 + * @param unZipPath 解压后的目录 + * @param zips 压缩文件流 + */ + private fun unZipFileByInput(unZipPath: String, zips: FileInputStream) { + val path = createSeparator(unZipPath) + var bos: BufferedOutputStream? = null + var zis: ZipInputStream? = null + try { + var filename: String + zis = ZipInputStream(BufferedInputStream(zips)) + var ze: ZipEntry + val buffer = ByteArray(BUFF_SIZE) + var count: Int + while (zis.nextEntry.also { ze = it } != null) { + filename = ze.name + createSubFolders(filename, path) + if (ze.isDirectory) { + val fmd = File(path + filename) + fmd.mkdirs() + continue + } + bos = BufferedOutputStream(FileOutputStream(path + filename)) + while (zis.read(buffer).also { count = it } != -1) bos.write(buffer, 0, count) + bos.flush() + bos.close() + } + } catch (_: IOException) { + } finally { + runCatching { + if (zis != null) { + zis.closeEntry() + zis.close() + } + bos?.close() + } + } + } + + /** @base code */ + private fun recursionZip(zipOut: ZipOutputStream, file: File, baseDir: String) { + if (file.isDirectory) { + val files = file.listFiles() ?: emptyArray() + for (fileSec in files) recursionZip(zipOut, fileSec, baseDir + file.name + File.separator) + } else { + val buf = ByteArray(1024) + val input: InputStream = FileInputStream(file) + zipOut.putNextEntry(ZipEntry(baseDir + file.name)) + var len: Int + while (input.read(buf).also { len = it } != -1) { + zipOut.write(buf, 0, len) + } + input.close() + } + } + + /** @base code */ + private fun createSubFolders(filename: String, path: String) { + val subFolders = filename.split("/").toTypedArray() + if (subFolders.size <= 1) return + var pathNow = path + for (i in 0 until subFolders.size - 1) { + pathNow = pathNow + subFolders[i] + "/" + val fmd = File(pathNow) + if (fmd.exists()) continue + fmd.mkdirs() + } + } + + /** @base code */ + private fun createSeparator(path: String): String { + val dir = File(path) + if (!dir.exists()) dir.mkdirs() + return if (path.endsWith("/")) path else "$path/" + } +} \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 22044c3..9682037 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -40,4 +40,6 @@ すべてのアラーレコードがクリアされました すべてのログファイルをエクスポートしてもよろしいですか? 梱包プロセスには時間がかかる場合があります。 詳細を見る + すべてのエラーレコードがエクスポートされました + すべてのエラーレコードのエクスポートに失敗しました \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 9118d3d..0d7d61c 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -40,4 +40,6 @@ 全部异常记录已清空 你确定要导出全部日志文件吗?打包过程可能会花费一点时间。 查看详情 + 已导出全部异常记录 + 导出全部异常记录失败 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index d6325f9..e7ea425 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -40,4 +40,6 @@ 全部異常紀錄已清空 你確認要導出全部日誌文件嗎?打包過程可能會花費一點時間。 查看詳情 + 已導出全部異常紀錄 + 導出全部異常紀錄失敗 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rMO/strings.xml b/app/src/main/res/values-zh-rMO/strings.xml index 027b7ea..14890aa 100644 --- a/app/src/main/res/values-zh-rMO/strings.xml +++ b/app/src/main/res/values-zh-rMO/strings.xml @@ -40,4 +40,6 @@ 全部異常紀錄已清空 你確認要導出全部日誌文件嗎?打包過程可能會花費一點時間。 查看詳情 + 已導出全部異常紀錄 + 導出全部異常紀錄失敗 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index dad2831..44d59eb 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -40,4 +40,6 @@ 全部異常紀錄已清空 你確認要導出全部日誌文件嗎?打包過程可能會花費一點時間。 查看詳情 + 已導出全部異常紀錄 + 導出全部異常紀錄失敗 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 10fcb13..d4d6706 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -39,4 +39,6 @@ All errors records have been cleared Are you sure you want to export all log files? The packaging process may take a while. View detail + All errors record exported + Failed to exported all errors record \ No newline at end of file