mirror of
https://github.com/KitsunePie/AppErrorsTracking.git
synced 2025-09-04 10:15:18 +08:00
feat: lots of changes
- add BuildConfigWrapper - add project promote - add ci version tag support - change app analytics config item show when available - fix system api compat issues
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
package com.fankes.apperrorstracking
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.fankes.apperrorstracking", appContext.packageName)
|
||||
}
|
||||
}
|
BIN
module-app/src/main/ic_launcher-playstore.png
Normal file
BIN
module-app/src/main/ic_launcher-playstore.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/10.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.application
|
||||
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import com.fankes.apperrorstracking.data.ConfigData
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.utils.tool.AppAnalyticsTool
|
||||
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication
|
||||
|
||||
class AppErrorsApplication : ModuleApplication() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
/** 跟随系统夜间模式 */
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
/** 绑定 I18n */
|
||||
LocaleString.bind(instance = this)
|
||||
/** 装载存储控制类 */
|
||||
ConfigData.init(instance = this)
|
||||
/** 装载 App Center */
|
||||
AppAnalyticsTool.init(instance = this)
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/1.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.bean
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* 应用异常信息显示 bean
|
||||
* @param pid APP 进程 ID
|
||||
* @param userId APP 用户 ID
|
||||
* @param packageName APP 包名
|
||||
* @param processName APP 进程名
|
||||
* @param appName APP 名称
|
||||
* @param title 标题
|
||||
* @param isShowAppInfoButton 是否显示应用信息按钮
|
||||
* @param isShowCloseAppButton 是否显示关闭应用按钮
|
||||
* @param isShowReopenButton 是否显示重新打开按钮
|
||||
*/
|
||||
data class AppErrorsDisplayBean(
|
||||
var pid: Int,
|
||||
var userId: Int,
|
||||
var packageName: String,
|
||||
var processName: String,
|
||||
var appName: String,
|
||||
var title: String,
|
||||
var isShowAppInfoButton: Boolean,
|
||||
var isShowCloseAppButton: Boolean,
|
||||
var isShowReopenButton: Boolean
|
||||
) : Serializable
|
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/10.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.bean
|
||||
|
||||
import android.app.ApplicationErrorReport
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.utils.factory.appCpuAbiOf
|
||||
import com.fankes.apperrorstracking.utils.factory.appVersionCodeOf
|
||||
import com.fankes.apperrorstracking.utils.factory.appVersionNameOf
|
||||
import com.fankes.apperrorstracking.utils.factory.difference
|
||||
import com.fankes.apperrorstracking.utils.factory.toUtcTime
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import java.io.Serializable
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* 应用异常信息 bean
|
||||
* @param pid 进程 ID
|
||||
* @param userId 用户 ID
|
||||
* @param cpuAbi CPU 架构类型
|
||||
* @param packageName 包名
|
||||
* @param versionName 版本名称
|
||||
* @param versionCode 版本号
|
||||
* @param isNativeCrash 是否为原生层异常
|
||||
* @param exceptionClassName 异常类名
|
||||
* @param exceptionMessage 异常信息
|
||||
* @param throwClassName 抛出异常的类名
|
||||
* @param throwFileName 抛出异常的文件名
|
||||
* @param throwMethodName 抛出异常的方法名
|
||||
* @param throwLineNumber 抛出异常的行号
|
||||
* @param stackTrace 异常堆栈
|
||||
* @param timestamp 记录时间戳
|
||||
*/
|
||||
data class AppErrorsInfoBean(
|
||||
@SerializedName("pid")
|
||||
var pid: Int = -1,
|
||||
@SerializedName("userId")
|
||||
var userId: Int = -1,
|
||||
@SerializedName("cpuAbi")
|
||||
var cpuAbi: String = "",
|
||||
@SerializedName("packageName")
|
||||
var packageName: String = "",
|
||||
@SerializedName("versionName")
|
||||
var versionName: String = "",
|
||||
@SerializedName("versionCode")
|
||||
var versionCode: Long = -1L,
|
||||
@SerializedName("isNativeCrash")
|
||||
var isNativeCrash: Boolean = false,
|
||||
@SerializedName("exceptionClassName")
|
||||
var exceptionClassName: String = "",
|
||||
@SerializedName("exceptionMessage")
|
||||
var exceptionMessage: String = "",
|
||||
@SerializedName("throwFileName")
|
||||
var throwFileName: String = "",
|
||||
@SerializedName("throwClassName")
|
||||
var throwClassName: String = "",
|
||||
@SerializedName("throwMethodName")
|
||||
var throwMethodName: String = "",
|
||||
@SerializedName("throwLineNumber")
|
||||
var throwLineNumber: Int = -1,
|
||||
@SerializedName("stackTrace")
|
||||
var stackTrace: String = "",
|
||||
@SerializedName("timestamp")
|
||||
var timestamp: Long = -1L
|
||||
) : Serializable {
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* 从 [ApplicationErrorReport.CrashInfo] 克隆
|
||||
* @param context 当前实例
|
||||
* @param pid APP 进程 ID
|
||||
* @param userId APP 用户 ID
|
||||
* @param packageName APP 包名
|
||||
* @param crashInfo [ApplicationErrorReport.CrashInfo]
|
||||
* @return [AppErrorsInfoBean]
|
||||
*/
|
||||
fun clone(context: Context, pid: Int, userId: Int, packageName: String?, crashInfo: ApplicationErrorReport.CrashInfo?) =
|
||||
(crashInfo?.exceptionClassName?.lowercase() == "native crash").let { isNativeCrash ->
|
||||
AppErrorsInfoBean(
|
||||
pid = pid,
|
||||
userId = userId,
|
||||
cpuAbi = packageName?.let { context.appCpuAbiOf(it) } ?: "",
|
||||
packageName = packageName ?: "unknown",
|
||||
versionName = packageName?.let { context.appVersionNameOf(it).ifBlank { "unknown" } } ?: "",
|
||||
versionCode = packageName?.let { context.appVersionCodeOf(it) } ?: -1L,
|
||||
isNativeCrash = isNativeCrash,
|
||||
exceptionClassName = crashInfo?.exceptionClassName ?: "unknown",
|
||||
exceptionMessage = if (isNativeCrash) crashInfo?.stackTrace.let {
|
||||
if (it?.contains("Abort message: '") == true)
|
||||
runCatching { it.split("Abort message: '")[1].split("'")[0] }.getOrNull()
|
||||
?: crashInfo?.exceptionMessage ?: "unknown"
|
||||
else crashInfo?.exceptionMessage ?: "unknown"
|
||||
} else crashInfo?.exceptionMessage ?: "unknown",
|
||||
throwFileName = crashInfo?.throwFileName ?: "unknown",
|
||||
throwClassName = crashInfo?.throwClassName ?: "unknown",
|
||||
throwMethodName = crashInfo?.throwMethodName ?: "unknown",
|
||||
throwLineNumber = crashInfo?.throwLineNumber ?: -1,
|
||||
stackTrace = crashInfo?.stackTrace?.trim() ?: "unknown",
|
||||
timestamp = System.currentTimeMillis()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前内容是否为空
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val isEmpty get() = pid == -1 && userId == -1 && timestamp == -1L
|
||||
|
||||
/**
|
||||
* 获取生成的 Json 文件名
|
||||
* @return [String]
|
||||
*/
|
||||
val jsonFileName get() = "${packageName}_${pid}_$timestamp.json"
|
||||
|
||||
/**
|
||||
* 获取 APP 版本信息与版本号
|
||||
* @return [String]
|
||||
*/
|
||||
val versionBrand get() = if (versionName.isBlank()) "unknown" else "$versionName($versionCode)"
|
||||
|
||||
/**
|
||||
* 获取异常本地化 UTC 时间
|
||||
* @return [String]
|
||||
*/
|
||||
val utcTime get() = timestamp.toUtcTime()
|
||||
|
||||
/**
|
||||
* 获取异常本地化经过时间
|
||||
* @return [String]
|
||||
*/
|
||||
val crossTime
|
||||
get() = timestamp.difference(
|
||||
now = LocaleString.momentAgo,
|
||||
second = LocaleString.secondAgo,
|
||||
minute = LocaleString.minuteAgo,
|
||||
hour = LocaleString.hourAgo,
|
||||
day = LocaleString.dayAgo,
|
||||
month = LocaleString.monthAgo,
|
||||
year = LocaleString.yearAgo
|
||||
)
|
||||
|
||||
/**
|
||||
* 获取异常本地化时间
|
||||
* @return [String]
|
||||
*/
|
||||
val dateTime get() = SimpleDateFormat.getDateTimeInstance().format(Date(timestamp)) ?: utcTime
|
||||
|
||||
/**
|
||||
* 获取异常堆栈分享模板
|
||||
* @return [String]
|
||||
*/
|
||||
val stackOutputShareContent
|
||||
get() = """
|
||||
Generated by AppErrorsTracking
|
||||
Project Url: https://github.com/KitsunePie/AppErrorsTracking
|
||||
===============
|
||||
$environmentInfo
|
||||
""".trimIndent()
|
||||
|
||||
/**
|
||||
* 获取异常堆栈文件模板
|
||||
* @return [String]
|
||||
*/
|
||||
val stackOutputFileContent
|
||||
get() = """
|
||||
================================================================
|
||||
Generated by AppErrorsTracking
|
||||
Project Url: https://github.com/KitsunePie/AppErrorsTracking
|
||||
================================================================
|
||||
""".trimIndent()
|
||||
|
||||
/**
|
||||
* 获取运行环境信息
|
||||
* @return [String]
|
||||
*/
|
||||
private val environmentInfo
|
||||
get() = """
|
||||
[Device Brand]: ${Build.BRAND}
|
||||
[Device Model]: ${Build.MODEL}
|
||||
[Display]: ${Build.DISPLAY}
|
||||
[Android Version]: ${Build.VERSION.RELEASE}
|
||||
[Android API Level]: ${Build.VERSION.SDK_INT}
|
||||
[System Locale]: ${Locale.getDefault()}
|
||||
[Process ID]: $pid
|
||||
[User ID]: $userId
|
||||
[CPU ABI]: ${cpuAbi.ifBlank { "none" }}
|
||||
[Package Name]: $packageName
|
||||
[Version Name]: ${versionName.ifBlank { "unknown" }}
|
||||
[Version Code]: ${versionCode.takeIf { it != -1L } ?: "unknown"}
|
||||
[Error Type]: ${if (isNativeCrash) "Native" else "JVM"}
|
||||
[Crash Time]: $utcTime
|
||||
[Stack Trace]:
|
||||
$stackTrace
|
||||
""".trimIndent()
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/4.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.bean
|
||||
|
||||
import com.fankes.apperrorstracking.bean.enum.AppFiltersType
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* 应用过滤条件 bean
|
||||
* @param name 名称或包名
|
||||
* @param type 过滤条件类型
|
||||
*/
|
||||
data class AppFiltersBean(
|
||||
var name: String = "",
|
||||
var type: AppFiltersType = AppFiltersType.USER
|
||||
) : Serializable
|
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/8.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.bean
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* 应用信息 bean
|
||||
* @param icon 图标
|
||||
* @param name APP 名称
|
||||
* @param packageName APP 包名
|
||||
*/
|
||||
data class AppInfoBean(
|
||||
var icon: Drawable? = null,
|
||||
var name: String,
|
||||
var packageName: String
|
||||
) : Serializable
|
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/3.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.bean
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* 已忽略异常的应用 bean
|
||||
* @param type 类型
|
||||
* @param packageName 包名
|
||||
*/
|
||||
data class MutedErrorsAppBean(
|
||||
var type: MuteType,
|
||||
var packageName: String
|
||||
) : Serializable {
|
||||
|
||||
/**
|
||||
* 已忽略的异常类型
|
||||
*/
|
||||
enum class MuteType { UNTIL_UNLOCKS, UNTIL_REBOOTS }
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2023/1/22.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.bean.enum
|
||||
|
||||
/**
|
||||
* 应用过滤条件类型定义类
|
||||
*/
|
||||
enum class AppFiltersType {
|
||||
/** 用户 */
|
||||
USER,
|
||||
|
||||
/** 系统 */
|
||||
SYSTEM,
|
||||
|
||||
/** 全部 */
|
||||
ALL
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2023/9/19.
|
||||
*/
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package com.fankes.apperrorstracking.const
|
||||
|
||||
import com.fankes.apperrorstracking.generated.ModuleAppProperties
|
||||
import com.fankes.apperrorstracking.wrapper.BuildConfigWrapper
|
||||
|
||||
/**
|
||||
* 包名常量定义类
|
||||
*/
|
||||
object PackageName {
|
||||
|
||||
/** 系统框架 */
|
||||
const val SYSTEM_FRAMEWORK = "android"
|
||||
}
|
||||
|
||||
/**
|
||||
* 模块版本常量定义类
|
||||
*/
|
||||
object ModuleVersion {
|
||||
|
||||
/** 当前 GitHub 提交的 ID (CI 自动构建) */
|
||||
const val GITHUB_COMMIT_ID = ModuleAppProperties.GITHUB_CI_COMMIT_ID
|
||||
|
||||
/** 版本名称 */
|
||||
const val NAME = BuildConfigWrapper.VERSION_NAME
|
||||
|
||||
/** 版本号 */
|
||||
const val CODE = BuildConfigWrapper.VERSION_CODE
|
||||
|
||||
/** 是否为 CI 自动构建版本 */
|
||||
val isCiMode = GITHUB_COMMIT_ID.isNotBlank()
|
||||
|
||||
/** 当前版本名称后缀 */
|
||||
val suffix = GITHUB_COMMIT_ID.let { if (it.isNotBlank()) "-$it" else "" }
|
||||
|
||||
override fun toString() = "$NAME$suffix($CODE)"
|
||||
}
|
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2023/1/20.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.data
|
||||
|
||||
import com.fankes.apperrorstracking.data.enum.AppErrorsConfigType
|
||||
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
|
||||
|
||||
/**
|
||||
* 应用配置模版存储控制类
|
||||
*/
|
||||
object AppErrorsConfigData {
|
||||
|
||||
/** 显示错误对话框键值名称 */
|
||||
private const val SHOW_ERRORS_DIALOG_APPS = "_show_errors_dialog_apps"
|
||||
|
||||
/** 推送错误通知键值名称 */
|
||||
private const val SHOW_ERRORS_NOTIFY_APPS = "_show_errors_notify_apps"
|
||||
|
||||
/** 显示错误 Toast 键值名称 */
|
||||
private const val SHOW_ERRORS_TOAST_APPS = "_show_errors_toast_apps"
|
||||
|
||||
/** 什么也不显示键值名称 */
|
||||
private const val SHOW_ERRORS_NOTHING_APPS = "_show_errors_nothing_apps"
|
||||
|
||||
/** 全局错误显示类型 */
|
||||
private val GLOBAL_SHOW_ERRORS_TYPE = PrefsData("_global_show_errors_type", AppErrorsConfigType.DIALOG.ordinal)
|
||||
|
||||
/** 显示错误对话框的 APP 包名数组 */
|
||||
private var showDialogApps = HashSet<String>()
|
||||
|
||||
/** 推送错误通知的 APP 包名数组 */
|
||||
private var showNotifyApps = HashSet<String>()
|
||||
|
||||
/** 显示错误 Toast 的 APP 包名数组 */
|
||||
private var showToastApps = HashSet<String>()
|
||||
|
||||
/** 什么也不显示的 APP 包名数组 */
|
||||
private var showNothingApps = HashSet<String>()
|
||||
|
||||
/** 刷新存储控制类 */
|
||||
fun refresh() {
|
||||
showDialogApps = ConfigData.getStringSet(SHOW_ERRORS_DIALOG_APPS).toHashSet()
|
||||
showNotifyApps = ConfigData.getStringSet(SHOW_ERRORS_NOTIFY_APPS).toHashSet()
|
||||
showToastApps = ConfigData.getStringSet(SHOW_ERRORS_TOAST_APPS).toHashSet()
|
||||
showNothingApps = ConfigData.getStringSet(SHOW_ERRORS_NOTHING_APPS).toHashSet()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 APP 显示错误的类型是否为 [type]
|
||||
* @param type 当前类型
|
||||
* @param packageName 当前 APP 包名 - 不填为全局配置
|
||||
* @return [Boolean]
|
||||
*/
|
||||
fun isAppShowingType(type: AppErrorsConfigType, packageName: String = "") =
|
||||
if (packageName.isNotBlank()) when (type) {
|
||||
AppErrorsConfigType.GLOBAL ->
|
||||
showDialogApps.contains(packageName).not() &&
|
||||
showNotifyApps.contains(packageName).not() &&
|
||||
showToastApps.contains(packageName).not() &&
|
||||
showNothingApps.contains(packageName).not()
|
||||
AppErrorsConfigType.DIALOG -> showDialogApps.contains(packageName)
|
||||
AppErrorsConfigType.NOTIFY -> showNotifyApps.contains(packageName)
|
||||
AppErrorsConfigType.TOAST -> showToastApps.contains(packageName)
|
||||
AppErrorsConfigType.NOTHING -> showNothingApps.contains(packageName)
|
||||
} else ConfigData.getInt(GLOBAL_SHOW_ERRORS_TYPE) == type.ordinal
|
||||
|
||||
/**
|
||||
* 写入当前 APP 显示错误的类型
|
||||
* @param type 当前类型
|
||||
* @param packageName 当前 APP 包名 - 不填为全局配置
|
||||
* @throws IllegalStateException 如果 [packageName] 为空 [type] 为 [AppErrorsConfigType.GLOBAL]
|
||||
*/
|
||||
fun putAppShowingType(type: AppErrorsConfigType, packageName: String = "") {
|
||||
if (packageName.isBlank() && type == AppErrorsConfigType.GLOBAL)
|
||||
error("You can't still specify the \"follow global config\" type when saving the global config")
|
||||
fun saveAllData() {
|
||||
ConfigData.putStringSet(SHOW_ERRORS_DIALOG_APPS, showDialogApps)
|
||||
ConfigData.putStringSet(SHOW_ERRORS_NOTIFY_APPS, showNotifyApps)
|
||||
ConfigData.putStringSet(SHOW_ERRORS_TOAST_APPS, showToastApps)
|
||||
ConfigData.putStringSet(SHOW_ERRORS_NOTHING_APPS, showNothingApps)
|
||||
}
|
||||
if (packageName.isNotBlank()) when (type) {
|
||||
AppErrorsConfigType.GLOBAL -> {
|
||||
showDialogApps.remove(packageName)
|
||||
showNotifyApps.remove(packageName)
|
||||
showToastApps.remove(packageName)
|
||||
showNothingApps.remove(packageName)
|
||||
saveAllData()
|
||||
}
|
||||
AppErrorsConfigType.DIALOG -> {
|
||||
showDialogApps.add(packageName)
|
||||
showNotifyApps.remove(packageName)
|
||||
showToastApps.remove(packageName)
|
||||
showNothingApps.remove(packageName)
|
||||
saveAllData()
|
||||
}
|
||||
AppErrorsConfigType.NOTIFY -> {
|
||||
showDialogApps.remove(packageName)
|
||||
showNotifyApps.add(packageName)
|
||||
showToastApps.remove(packageName)
|
||||
showNothingApps.remove(packageName)
|
||||
saveAllData()
|
||||
}
|
||||
AppErrorsConfigType.TOAST -> {
|
||||
showDialogApps.remove(packageName)
|
||||
showNotifyApps.remove(packageName)
|
||||
showToastApps.add(packageName)
|
||||
showNothingApps.remove(packageName)
|
||||
saveAllData()
|
||||
}
|
||||
AppErrorsConfigType.NOTHING -> {
|
||||
showDialogApps.remove(packageName)
|
||||
showNotifyApps.remove(packageName)
|
||||
showToastApps.remove(packageName)
|
||||
showNothingApps.add(packageName)
|
||||
saveAllData()
|
||||
}
|
||||
} else ConfigData.putInt(GLOBAL_SHOW_ERRORS_TYPE, type.ordinal)
|
||||
}
|
||||
}
|
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2023/1/17.
|
||||
*/
|
||||
@file:Suppress("StaticFieldLeak")
|
||||
|
||||
package com.fankes.apperrorstracking.data
|
||||
|
||||
import android.content.Context
|
||||
import android.provider.Settings
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
|
||||
import com.fankes.apperrorstracking.utils.factory.appCpuAbiOf
|
||||
import com.fankes.apperrorstracking.utils.factory.appVersionCodeOf
|
||||
import com.fankes.apperrorstracking.utils.factory.appVersionNameOf
|
||||
import com.fankes.apperrorstracking.utils.factory.toEntityOrNull
|
||||
import com.fankes.apperrorstracking.utils.factory.toJsonOrNull
|
||||
import com.highcapable.yukihookapi.hook.log.loggerE
|
||||
import java.io.File
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
/**
|
||||
* [AppErrorsInfoBean] 存储控制类
|
||||
*/
|
||||
object AppErrorsRecordData {
|
||||
|
||||
/** 异常记录数据文件目录路径 */
|
||||
private const val FOLDER_PATH = "/data/misc/app_errors_records/"
|
||||
|
||||
/** 当前实例 */
|
||||
private var context: Context? = null
|
||||
|
||||
/**
|
||||
* 获取当前异常记录数据目录
|
||||
* @return [File]
|
||||
*/
|
||||
private val errorsInfoDataFolder by lazy { File(FOLDER_PATH) }
|
||||
|
||||
/**
|
||||
* 获取当前全部异常记录数据文件
|
||||
* @return [List]<[File]>
|
||||
*/
|
||||
private val errorsInfoDataFiles get() = errorsInfoDataFolder.listFiles()?.sortedByDescending { it.lastModified() } ?: emptyList()
|
||||
|
||||
/** 已记录的全部 APP 异常信息数组 */
|
||||
var allData = CopyOnWriteArrayList<AppErrorsInfoBean>()
|
||||
|
||||
/**
|
||||
* 初始化存储控制类
|
||||
* @param context 实例
|
||||
*/
|
||||
fun init(context: Context) {
|
||||
this.context = context
|
||||
initializeDataDirectory()
|
||||
allData = readAllDataFromFiles()
|
||||
}
|
||||
|
||||
/** 初始化异常记录数据目录 */
|
||||
private fun initializeDataDirectory() {
|
||||
runCatching {
|
||||
errorsInfoDataFolder.also { if (it.exists().not() || it.isFile) it.apply { delete(); mkdirs() } }
|
||||
}.onFailure {
|
||||
loggerE(msg = "Can't create directory \"$FOLDER_PATH\", there will be problems with the app errors records function", e = it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取旧版异常记录数据并自动转换到新版
|
||||
* @return [ArrayList]<[AppErrorsInfoBean]> or null
|
||||
*/
|
||||
private fun copyOldDataFromResolverString() = context?.let {
|
||||
val keyName = "app_errors_data"
|
||||
runCatching {
|
||||
Settings.Secure.getString(it.contentResolver, keyName)
|
||||
?.toEntityOrNull<CopyOnWriteArrayList<AppErrorsInfoBean>>()
|
||||
?.onEach { e ->
|
||||
e.cpuAbi = it.appCpuAbiOf(e.packageName)
|
||||
e.versionName = it.appVersionNameOf(e.packageName).ifBlank { "unknown" }
|
||||
e.versionCode = it.appVersionCodeOf(e.packageName)
|
||||
e.toJsonOrNull()?.also { json -> File(errorsInfoDataFolder.absolutePath, e.jsonFileName).writeText(json) }
|
||||
}.let { result ->
|
||||
if (result != null) {
|
||||
Settings.Secure.putString(it.contentResolver, keyName, "")
|
||||
result
|
||||
} else null
|
||||
}
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件获取全部异常记录数据
|
||||
* @return [ArrayList]<[AppErrorsInfoBean]>
|
||||
*/
|
||||
private fun readAllDataFromFiles() = copyOldDataFromResolverString() ?: CopyOnWriteArrayList<AppErrorsInfoBean>().apply {
|
||||
errorsInfoDataFiles.takeIf { it.isNotEmpty() }?.forEach { it.readText().toEntityOrNull<AppErrorsInfoBean>()?.let { e -> add(e) } }
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加新的异常记录数据
|
||||
* @param bean [AppErrorsInfoBean] 实例
|
||||
*/
|
||||
fun add(bean: AppErrorsInfoBean) {
|
||||
allData.add(0, bean)
|
||||
bean.toJsonOrNull()?.runCatching { File(errorsInfoDataFolder.absolutePath, bean.jsonFileName).writeText(this) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定的异常记录数据
|
||||
* @param bean [AppErrorsInfoBean] 实例
|
||||
*/
|
||||
fun remove(bean: AppErrorsInfoBean) {
|
||||
allData.remove(bean)
|
||||
runCatching { File(errorsInfoDataFolder.absolutePath, bean.jsonFileName).delete() }
|
||||
}
|
||||
|
||||
/** 清除全部异常记录数据 */
|
||||
fun clearAll() {
|
||||
allData.clear()
|
||||
runCatching { errorsInfoDataFolder.deleteRecursively() }
|
||||
initializeDataDirectory()
|
||||
}
|
||||
}
|
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/10/1.
|
||||
*/
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package com.fankes.apperrorstracking.data
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import com.highcapable.yukihookapi.hook.factory.prefs
|
||||
import com.highcapable.yukihookapi.hook.log.loggerW
|
||||
import com.highcapable.yukihookapi.hook.param.PackageParam
|
||||
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
|
||||
|
||||
/**
|
||||
* 全局配置存储控制类
|
||||
*/
|
||||
object ConfigData {
|
||||
|
||||
/** 显示开发者提示 */
|
||||
val SHOW_DEVELOPER_NOTICE = PrefsData("_show_developer_notice", true)
|
||||
|
||||
/** 启用 Material 3 风格的错误对话框 */
|
||||
val ENABLE_MATERIAL3_STYLE_APP_ERRORS_DIALOG = PrefsData("_enable_material3_style_dialog", Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
|
||||
/** 仅对前台应用显示错误对话框 */
|
||||
val ENABLE_ONLY_SHOW_ERRORS_IN_FRONT = PrefsData("_enable_only_show_errors_in_front", false)
|
||||
|
||||
/** 仅对应用主进程显示错误对话框 */
|
||||
val ENABLE_ONLY_SHOW_ERRORS_IN_MAIN = PrefsData("_enable_only_show_errors_in_main", false)
|
||||
|
||||
/** 错误对话框始终显示“重新打开”选项 */
|
||||
val ENABLE_ALWAYS_SHOWS_REOPEN_APP_OPTIONS = PrefsData("_enable_always_shows_reopen_app_options", false)
|
||||
|
||||
/** 启用应用配置模板 */
|
||||
val ENABLE_APP_CONFIG_TEMPLATE = PrefsData("_enable_app_config_template", false)
|
||||
|
||||
/** 禁止异常堆栈内容自动换行 */
|
||||
val DISABLE_AUTO_WRAP_ERROR_STACK_TRACE = PrefsData("_disable_auto_wrap_error_stack_trace", false)
|
||||
|
||||
/** 当前实例 - [Context] or [PackageParam] */
|
||||
private var instance: Any? = null
|
||||
|
||||
/**
|
||||
* 初始化存储控制类
|
||||
* @param instance 实例 - 只能是 [Context] or [PackageParam]
|
||||
* @throws IllegalStateException 如果类型错误
|
||||
*/
|
||||
fun init(instance: Any) {
|
||||
when (instance) {
|
||||
is Context, is PackageParam -> this.instance = instance
|
||||
else -> error("Unknown type for init ConfigData")
|
||||
}
|
||||
AppErrorsConfigData.refresh()
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 [Set]<[String]> 数据
|
||||
* @param key 键值名称
|
||||
* @return [Set]<[String]>
|
||||
*/
|
||||
internal fun getStringSet(key: String) = when (instance) {
|
||||
is Context -> (instance as Context).prefs().getStringSet(key, setOf())
|
||||
is PackageParam -> (instance as PackageParam).prefs.getStringSet(key, setOf())
|
||||
else -> error("Unknown type for get prefs data")
|
||||
}
|
||||
|
||||
/**
|
||||
* 存入 [Set]<[String]> 数据
|
||||
* @param key 键值名称
|
||||
* @param value 键值内容
|
||||
*/
|
||||
internal fun putStringSet(key: String, value: Set<String>) {
|
||||
when (instance) {
|
||||
is Context -> (instance as Context).prefs().edit { putStringSet(key, value) }
|
||||
is PackageParam -> loggerW(msg = "Not support for this method")
|
||||
else -> error("Unknown type for put prefs data")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 [Int] 数据
|
||||
* @param data 键值数据模板
|
||||
* @return [Int]
|
||||
*/
|
||||
internal fun getInt(data: PrefsData<Int>) = when (instance) {
|
||||
is Context -> (instance as Context).prefs().get(data)
|
||||
is PackageParam -> (instance as PackageParam).prefs.get(data)
|
||||
else -> error("Unknown type for get prefs data")
|
||||
}
|
||||
|
||||
/**
|
||||
* 存入 [Int] 数据
|
||||
* @param data 键值数据模板
|
||||
* @param value 键值内容
|
||||
*/
|
||||
internal fun putInt(data: PrefsData<Int>, value: Int) {
|
||||
when (instance) {
|
||||
is Context -> (instance as Context).prefs().edit { put(data, value) }
|
||||
is PackageParam -> loggerW(msg = "Not support for this method")
|
||||
else -> error("Unknown type for put prefs data")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 [Boolean] 数据
|
||||
* @param data 键值数据模板
|
||||
* @return [Boolean]
|
||||
*/
|
||||
internal fun getBoolean(data: PrefsData<Boolean>) = when (instance) {
|
||||
is Context -> (instance as Context).prefs().get(data)
|
||||
is PackageParam -> (instance as PackageParam).prefs.get(data)
|
||||
else -> error("Unknown type for get prefs data")
|
||||
}
|
||||
|
||||
/**
|
||||
* 存入 [Boolean] 数据
|
||||
* @param data 键值数据模板
|
||||
* @param value 键值内容
|
||||
*/
|
||||
internal fun putBoolean(data: PrefsData<Boolean>, value: Boolean) {
|
||||
when (instance) {
|
||||
is Context -> (instance as Context).prefs().edit { put(data, value) }
|
||||
is PackageParam -> loggerW(msg = "Not support for this method")
|
||||
else -> error("Unknown type for put prefs data")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否显示开发者提示
|
||||
* @return [Boolean]
|
||||
*/
|
||||
var isShowDeveloperNotice
|
||||
get() = getBoolean(SHOW_DEVELOPER_NOTICE)
|
||||
set(value) {
|
||||
putBoolean(SHOW_DEVELOPER_NOTICE, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否仅对前台应用显示错误对话框
|
||||
* @return [Boolean]
|
||||
*/
|
||||
var isEnableOnlyShowErrorsInFront
|
||||
get() = getBoolean(ENABLE_ONLY_SHOW_ERRORS_IN_FRONT)
|
||||
set(value) {
|
||||
putBoolean(ENABLE_ONLY_SHOW_ERRORS_IN_FRONT, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否仅对应用主进程显示错误对话框
|
||||
* @return [Boolean]
|
||||
*/
|
||||
var isEnableOnlyShowErrorsInMain
|
||||
get() = getBoolean(ENABLE_ONLY_SHOW_ERRORS_IN_MAIN)
|
||||
set(value) {
|
||||
putBoolean(ENABLE_ONLY_SHOW_ERRORS_IN_MAIN, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误对话框是否始终显示“重新打开”选项
|
||||
* @return [Boolean]
|
||||
*/
|
||||
var isEnableAlwaysShowsReopenAppOptions
|
||||
get() = getBoolean(ENABLE_ALWAYS_SHOWS_REOPEN_APP_OPTIONS)
|
||||
set(value) {
|
||||
putBoolean(ENABLE_ALWAYS_SHOWS_REOPEN_APP_OPTIONS, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否启用应用配置模板
|
||||
* @return [Boolean]
|
||||
*/
|
||||
var isEnableAppConfigTemplate
|
||||
get() = getBoolean(ENABLE_APP_CONFIG_TEMPLATE)
|
||||
set(value) {
|
||||
putBoolean(ENABLE_APP_CONFIG_TEMPLATE, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否启用 Material 3 风格的错误对话框
|
||||
* @return [Boolean]
|
||||
*/
|
||||
var isEnableMaterial3StyleAppErrorsDialog
|
||||
get() = getBoolean(ENABLE_MATERIAL3_STYLE_APP_ERRORS_DIALOG)
|
||||
set(value) {
|
||||
putBoolean(ENABLE_MATERIAL3_STYLE_APP_ERRORS_DIALOG, value)
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2023/1/20.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.data.enum
|
||||
|
||||
/**
|
||||
* 应用配置模版类型定义类
|
||||
*/
|
||||
enum class AppErrorsConfigType {
|
||||
/** 跟随全局配置 */
|
||||
GLOBAL,
|
||||
|
||||
/** 对话框 */
|
||||
DIALOG,
|
||||
|
||||
/** 通知 */
|
||||
NOTIFY,
|
||||
|
||||
/** Toast */
|
||||
TOAST,
|
||||
|
||||
/** 什么也不显示 */
|
||||
NOTHING
|
||||
}
|
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2023/2/3.
|
||||
*/
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package com.fankes.apperrorstracking.data.factory
|
||||
|
||||
import android.widget.CompoundButton
|
||||
import com.fankes.apperrorstracking.data.ConfigData
|
||||
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
|
||||
|
||||
/**
|
||||
* 绑定到 [CompoundButton] 自动设置选中状态
|
||||
* @param data 键值数据模板
|
||||
* @param initiate 方法体
|
||||
*/
|
||||
fun CompoundButton.bind(data: PrefsData<Boolean>, initiate: CompoundButtonDataBinder.(CompoundButton) -> Unit = {}) {
|
||||
val binder = CompoundButtonDataBinder(button = this).also { initiate(it, this) }
|
||||
isChecked = ConfigData.getBoolean(data).also { binder.initializeCallback?.invoke(it) }
|
||||
binder.applyChangesCallback = { ConfigData.putBoolean(data, it) }
|
||||
setOnCheckedChangeListener { button, isChecked ->
|
||||
if (button.isPressed) {
|
||||
if (binder.isAutoApplyChanges) binder.applyChangesCallback?.invoke(isChecked)
|
||||
binder.changedCallback?.invoke(isChecked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [CompoundButton] 数据绑定管理器实例
|
||||
* @param button 当前实例
|
||||
*/
|
||||
class CompoundButtonDataBinder(private val button: CompoundButton) {
|
||||
|
||||
/** 状态初始化回调事件 */
|
||||
internal var initializeCallback: ((Boolean) -> Unit)? = null
|
||||
|
||||
/** 状态改变回调事件 */
|
||||
internal var changedCallback: ((Boolean) -> Unit)? = null
|
||||
|
||||
/** 应用更改回调事件 */
|
||||
internal var applyChangesCallback: ((Boolean) -> Unit)? = null
|
||||
|
||||
/** 是否启用自动应用更改 */
|
||||
var isAutoApplyChanges = true
|
||||
|
||||
/**
|
||||
* 监听状态初始化
|
||||
* @param result 回调结果
|
||||
*/
|
||||
fun onInitialize(result: (Boolean) -> Unit) {
|
||||
initializeCallback = result
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听状态改变
|
||||
* @param result 回调结果
|
||||
*/
|
||||
fun onChanged(result: (Boolean) -> Unit) {
|
||||
changedCallback = result
|
||||
}
|
||||
|
||||
/** 重新初始化 */
|
||||
fun reinitialize() {
|
||||
initializeCallback?.invoke(button.isChecked)
|
||||
}
|
||||
|
||||
/** 应用更改并重新初始化 */
|
||||
fun applyChangesAndReinitialize() {
|
||||
applyChanges()
|
||||
reinitialize()
|
||||
}
|
||||
|
||||
/** 应用更改 */
|
||||
fun applyChanges() {
|
||||
applyChangesCallback?.invoke(button.isChecked)
|
||||
}
|
||||
|
||||
/** 取消更改 */
|
||||
fun cancelChanges() {
|
||||
button.isChecked = button.isChecked.not()
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/7.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.hook
|
||||
|
||||
import com.fankes.apperrorstracking.data.ConfigData
|
||||
import com.fankes.apperrorstracking.hook.entity.FrameworkHooker
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed
|
||||
import com.highcapable.yukihookapi.hook.factory.configs
|
||||
import com.highcapable.yukihookapi.hook.factory.encase
|
||||
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
|
||||
|
||||
@InjectYukiHookWithXposed(entryClassName = "AppErrorsTracking", isUsingResourcesHook = false)
|
||||
object HookEntry : IYukiHookXposedInit {
|
||||
|
||||
override fun onInit() = configs {
|
||||
debugLog {
|
||||
tag = "AppErrorsTracking"
|
||||
isRecord = true
|
||||
}
|
||||
isDebug = false
|
||||
}
|
||||
|
||||
override fun onHook() = encase {
|
||||
loadSystem {
|
||||
LocaleString.bind(instance = this)
|
||||
ConfigData.init(instance = this)
|
||||
loadHooker(FrameworkHooker)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,465 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/7.
|
||||
*/
|
||||
@file:Suppress("ConstPropertyName")
|
||||
|
||||
package com.fankes.apperrorstracking.hook.entity
|
||||
|
||||
import android.app.ApplicationErrorReport
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.os.Build
|
||||
import android.os.Message
|
||||
import android.os.SystemClock
|
||||
import android.util.ArrayMap
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsDisplayBean
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
|
||||
import com.fankes.apperrorstracking.bean.AppInfoBean
|
||||
import com.fankes.apperrorstracking.bean.MutedErrorsAppBean
|
||||
import com.fankes.apperrorstracking.bean.enum.AppFiltersType
|
||||
import com.fankes.apperrorstracking.data.AppErrorsConfigData
|
||||
import com.fankes.apperrorstracking.data.AppErrorsRecordData
|
||||
import com.fankes.apperrorstracking.data.ConfigData
|
||||
import com.fankes.apperrorstracking.data.enum.AppErrorsConfigType
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsDisplayActivity
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsRecordActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.appNameOf
|
||||
import com.fankes.apperrorstracking.utils.factory.drawableOf
|
||||
import com.fankes.apperrorstracking.utils.factory.isAppCanOpened
|
||||
import com.fankes.apperrorstracking.utils.factory.listOfPackages
|
||||
import com.fankes.apperrorstracking.utils.factory.openApp
|
||||
import com.fankes.apperrorstracking.utils.factory.pushNotify
|
||||
import com.fankes.apperrorstracking.utils.factory.toArrayList
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
import com.fankes.apperrorstracking.wrapper.BuildConfigWrapper
|
||||
import com.highcapable.yukihookapi.hook.bean.VariousClass
|
||||
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
|
||||
import com.highcapable.yukihookapi.hook.factory.field
|
||||
import com.highcapable.yukihookapi.hook.factory.hasMethod
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.log.loggerE
|
||||
import com.highcapable.yukihookapi.hook.log.loggerI
|
||||
import com.highcapable.yukihookapi.hook.log.loggerW
|
||||
import com.highcapable.yukihookapi.hook.type.android.BundleClass
|
||||
import com.highcapable.yukihookapi.hook.type.android.MessageClass
|
||||
import com.highcapable.yukihookapi.hook.type.java.BooleanType
|
||||
|
||||
object FrameworkHooker : YukiBaseHooker() {
|
||||
|
||||
private const val ActivityManagerServiceClass = "com.android.server.am.ActivityManagerService"
|
||||
private const val UserControllerClass = "com.android.server.am.UserController"
|
||||
private const val AppErrorsClass = "com.android.server.am.AppErrors"
|
||||
private const val AppErrorDialogClass = "com.android.server.am.AppErrorDialog"
|
||||
private const val AppErrorDialog_DataClass = "com.android.server.am.AppErrorDialog\$Data"
|
||||
private const val ProcessRecordClass = "com.android.server.am.ProcessRecord"
|
||||
private const val ActivityTaskManagerService_LocalServiceClass = "com.android.server.wm.ActivityTaskManagerService\$LocalService"
|
||||
|
||||
private val PackageListClass = VariousClass(
|
||||
"com.android.server.am.ProcessRecord\$PackageList",
|
||||
"com.android.server.am.PackageList"
|
||||
)
|
||||
private val ErrorDialogControllerClass = VariousClass(
|
||||
"com.android.server.am.ProcessRecord\$ErrorDialogController",
|
||||
"com.android.server.am.ErrorDialogController"
|
||||
)
|
||||
|
||||
/** 已忽略错误的 APP 数组 - 直到重新解锁 */
|
||||
private var mutedErrorsIfUnlockApps = HashSet<String>()
|
||||
|
||||
/** 已忽略错误的 APP 数组 - 直到重新启动 */
|
||||
private var mutedErrorsIfRestartApps = HashSet<String>()
|
||||
|
||||
/**
|
||||
* APP 进程异常数据定义类
|
||||
* @param errors [AppErrorsClass] 实例
|
||||
* @param proc [ProcessRecordClass] 实例
|
||||
* @param resultData [AppErrorDialog_DataClass] 实例 - 默认空
|
||||
*/
|
||||
private class AppErrorsProcessData(errors: Any?, proc: Any?, resultData: Any? = null) {
|
||||
|
||||
/**
|
||||
* 获取当前包列表实例
|
||||
* @return [Any] or null
|
||||
*/
|
||||
private val pkgList = if (ProcessRecordClass.toClass().hasMethod { name = "getPkgList"; emptyParam() })
|
||||
ProcessRecordClass.toClass().method { name = "getPkgList"; emptyParam() }.get(proc).call()
|
||||
else ProcessRecordClass.toClass().field { name = "pkgList" }.get(proc).any()
|
||||
|
||||
/**
|
||||
* 获取当前包列表数组大小
|
||||
* @return [Int]
|
||||
*/
|
||||
private val pkgListSize = PackageListClass.toClassOrNull()?.method { name = "size"; emptyParam() }?.get(pkgList)?.int()
|
||||
?: ProcessRecordClass.toClass().field { name = "pkgList" }.get(proc).cast<ArrayMap<*, *>>()?.size ?: -1
|
||||
|
||||
/**
|
||||
* 获取当前 pid 信息
|
||||
* @return [Int]
|
||||
*/
|
||||
val pid = ProcessRecordClass.toClass().field { name { it == "mPid" || it == "pid" } }.get(proc).int()
|
||||
|
||||
/**
|
||||
* 获取当前用户 ID 信息
|
||||
* @return [Int]
|
||||
*/
|
||||
val userId = ProcessRecordClass.toClass().field { name = "userId" }.get(proc).int()
|
||||
|
||||
/**
|
||||
* 获取当前 APP 信息
|
||||
* @return [ApplicationInfo] or null
|
||||
*/
|
||||
val appInfo = ProcessRecordClass.toClass().field { name = "info" }.get(proc).cast<ApplicationInfo>()
|
||||
|
||||
/**
|
||||
* 获取当前进程名称
|
||||
* @return [String]
|
||||
*/
|
||||
val processName = ProcessRecordClass.toClass().field { name = "processName" }.get(proc).string()
|
||||
|
||||
/**
|
||||
* 获取当前 APP、进程 包名
|
||||
* @return [String]
|
||||
*/
|
||||
val packageName = appInfo?.packageName ?: processName
|
||||
|
||||
/**
|
||||
* 获取当前进程是否为可被启动的 APP - 非框架 APP
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val isActualApp = pkgListSize == 1 && appInfo != null
|
||||
|
||||
/**
|
||||
* 获取当前进程是否为主进程
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val isMainProcess = packageName == processName
|
||||
|
||||
/**
|
||||
* 获取当前进程是否为后台进程
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val isBackgroundProcess = UserControllerClass.toClass()
|
||||
.method { name { it == "getCurrentProfileIds" || it == "getCurrentProfileIdsLocked" } }
|
||||
.get(ActivityManagerServiceClass.toClass().field { name = "mUserController" }
|
||||
.get(AppErrorsClass.toClass().field { name = "mService" }.get(errors).any()).any())
|
||||
.invoke<IntArray>()?.takeIf { it.isNotEmpty() }?.any { it != userId } ?: false
|
||||
|
||||
/**
|
||||
* 获取当前进程是否短时内重复崩溃
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val isRepeatingCrash = resultData?.let { AppErrorDialog_DataClass.toClass().field { name = "repeating" }.get(it).boolean() } ?: false
|
||||
}
|
||||
|
||||
/** 注册生命周期 */
|
||||
private fun registerLifecycle() {
|
||||
onAppLifecycle {
|
||||
/** 解锁后清空已记录的忽略错误 APP */
|
||||
registerReceiver(Intent.ACTION_USER_PRESENT) { _, _ -> mutedErrorsIfUnlockApps.clear() }
|
||||
/** 刷新模块 Resources 缓存 */
|
||||
registerReceiver(Intent.ACTION_LOCALE_CHANGED) { _, _ -> refreshModuleAppResources() }
|
||||
/** 启动时从本地获取异常记录数据 */
|
||||
onCreate { AppErrorsRecordData.init(context = this) }
|
||||
}
|
||||
FrameworkTool.Host.with(instance = this) {
|
||||
onRefreshFrameworkPrefsData {
|
||||
/** 必要的延迟防止 Sp 存储不刷新 */
|
||||
SystemClock.sleep(100)
|
||||
/** 刷新存储类 */
|
||||
AppErrorsConfigData.refresh()
|
||||
if (prefs.isPreferencesAvailable.not()) loggerW(msg = "Cannot refreshing app errors config data, preferences is not available")
|
||||
}
|
||||
onOpenAppUsedFramework {
|
||||
appContext?.openApp(it.first, it.second)
|
||||
loggerI(msg = "Opened \"${it.first}\"${it.second.takeIf { e -> e > 0 }?.let { e -> " --user $e" } ?: ""}")
|
||||
}
|
||||
onPushAppErrorInfoData {
|
||||
AppErrorsRecordData.allData.firstOrNull { e -> e.pid == it } ?: run {
|
||||
loggerW(msg = "Cannot received crash application data --pid $it")
|
||||
AppErrorsInfoBean()
|
||||
}
|
||||
}
|
||||
onPushAppErrorsInfoData { AppErrorsRecordData.allData.toArrayList() }
|
||||
onRemoveAppErrorsInfoData {
|
||||
loggerI(msg = "Removed app errors info data for package \"${it.packageName}\"")
|
||||
AppErrorsRecordData.remove(it)
|
||||
}
|
||||
onClearAppErrorsInfoData {
|
||||
loggerI(msg = "Cleared all app errors info data, size ${AppErrorsRecordData.allData.size}")
|
||||
AppErrorsRecordData.clearAll()
|
||||
}
|
||||
onMutedErrorsIfUnlock {
|
||||
mutedErrorsIfUnlockApps.add(it)
|
||||
loggerI(msg = "Muted \"$it\" until unlocks")
|
||||
}
|
||||
onMutedErrorsIfRestart {
|
||||
mutedErrorsIfRestartApps.add(it)
|
||||
loggerI(msg = "Muted \"$it\" until restarts")
|
||||
}
|
||||
onPushMutedErrorsAppsData {
|
||||
arrayListOf<MutedErrorsAppBean>().apply {
|
||||
mutedErrorsIfUnlockApps.takeIf { it.isNotEmpty() }
|
||||
?.forEach { add(MutedErrorsAppBean(MutedErrorsAppBean.MuteType.UNTIL_UNLOCKS, it)) }
|
||||
mutedErrorsIfRestartApps.takeIf { it.isNotEmpty() }
|
||||
?.forEach { add(MutedErrorsAppBean(MutedErrorsAppBean.MuteType.UNTIL_REBOOTS, it)) }
|
||||
}
|
||||
}
|
||||
onUnmuteErrorsApp {
|
||||
when (it.type) {
|
||||
MutedErrorsAppBean.MuteType.UNTIL_UNLOCKS -> {
|
||||
loggerI(msg = "Unmuted if unlocks errors app \"${it.packageName}\"")
|
||||
mutedErrorsIfUnlockApps.remove(it.packageName)
|
||||
}
|
||||
MutedErrorsAppBean.MuteType.UNTIL_REBOOTS -> {
|
||||
loggerI(msg = "Unmuted if restarts errors app \"${it.packageName}\"")
|
||||
mutedErrorsIfRestartApps.remove(it.packageName)
|
||||
}
|
||||
}
|
||||
}
|
||||
onUnmuteAllErrorsApps {
|
||||
loggerI(msg = "Unmute all errors apps --unlocks ${mutedErrorsIfUnlockApps.size} --restarts ${mutedErrorsIfRestartApps.size}")
|
||||
mutedErrorsIfUnlockApps.clear()
|
||||
mutedErrorsIfRestartApps.clear()
|
||||
}
|
||||
onPushAppListData { filters ->
|
||||
appContext?.let { context ->
|
||||
context.listOfPackages()
|
||||
.filter { it.packageName.let { e -> e != "android" && e != BuildConfigWrapper.APPLICATION_ID } }
|
||||
.let { info ->
|
||||
arrayListOf<AppInfoBean>().apply {
|
||||
if (info.isNotEmpty())
|
||||
(if (filters.name.isNotBlank()) info.filter {
|
||||
it.packageName.contains(filters.name) || context.appNameOf(it.packageName).contains(filters.name)
|
||||
} else info).let { result ->
|
||||
/**
|
||||
* 是否为系统应用
|
||||
* @return [Boolean]
|
||||
*/
|
||||
fun PackageInfo.isSystemApp() = (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0
|
||||
when (filters.type) {
|
||||
AppFiltersType.USER -> result.filter { it.isSystemApp().not() }
|
||||
AppFiltersType.SYSTEM -> result.filter { it.isSystemApp() }
|
||||
AppFiltersType.ALL -> result
|
||||
}
|
||||
}.sortedByDescending { it.lastUpdateTime }
|
||||
.forEach { add(AppInfoBean(name = context.appNameOf(it.packageName), packageName = it.packageName)) }
|
||||
else loggerW(msg = "Fetched installed packages but got empty list")
|
||||
}
|
||||
}
|
||||
} ?: arrayListOf()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 APP 进程异常信息展示
|
||||
* @param context 当前实例
|
||||
*/
|
||||
private fun AppErrorsProcessData.handleShowAppErrorUi(context: Context) {
|
||||
/** 当前 APP 名称 */
|
||||
val appName = appInfo?.let { context.appNameOf(it.packageName).ifBlank { it.packageName } } ?: packageName
|
||||
|
||||
/** 当前 APP 名称 (包含用户 ID) */
|
||||
val appNameWithUserId = if (userId != 0) "$appName (${LocaleString.userId(userId)})" else appName
|
||||
|
||||
/** 崩溃标题 */
|
||||
val errorTitle = if (isRepeatingCrash) LocaleString.aerrRepeatedTitle(appNameWithUserId) else LocaleString.aerrTitle(appNameWithUserId)
|
||||
|
||||
/** 使用通知推送异常信息 */
|
||||
fun showAppErrorsWithNotify() =
|
||||
context.pushNotify(
|
||||
channelId = "APPS_ERRORS",
|
||||
channelName = LocaleString.appName,
|
||||
title = errorTitle,
|
||||
content = LocaleString.appErrorsTip,
|
||||
icon = IconCompat.createWithBitmap(moduleAppResources.drawableOf(R.drawable.ic_notify).toBitmap()),
|
||||
color = 0xFFFF6200.toInt(),
|
||||
intent = AppErrorsRecordActivity.intent()
|
||||
)
|
||||
|
||||
/** 使用 Toast 展示异常信息 */
|
||||
fun showAppErrorsWithToast() = context.toast(errorTitle)
|
||||
|
||||
/** 使用对话框展示异常信息 */
|
||||
fun showAppErrorsWithDialog() =
|
||||
AppErrorsDisplayActivity.start(
|
||||
context, AppErrorsDisplayBean(
|
||||
pid = pid,
|
||||
userId = userId,
|
||||
packageName = packageName,
|
||||
processName = processName,
|
||||
appName = appName,
|
||||
title = errorTitle,
|
||||
isShowAppInfoButton = isActualApp,
|
||||
isShowReopenButton = isActualApp &&
|
||||
(isRepeatingCrash.not() || ConfigData.isEnableAlwaysShowsReopenAppOptions) &&
|
||||
context.isAppCanOpened(packageName) &&
|
||||
isMainProcess,
|
||||
isShowCloseAppButton = isActualApp
|
||||
)
|
||||
)
|
||||
/** 判断是否为已忽略的 APP */
|
||||
if (mutedErrorsIfUnlockApps.contains(packageName) || mutedErrorsIfRestartApps.contains(packageName)) return
|
||||
/** 判断是否为后台进程 */
|
||||
if ((isBackgroundProcess || context.isAppCanOpened(packageName).not()) && ConfigData.isEnableOnlyShowErrorsInFront) return
|
||||
/** 判断是否为主进程 */
|
||||
if (isMainProcess.not() && ConfigData.isEnableOnlyShowErrorsInMain) return
|
||||
when {
|
||||
packageName == BuildConfigWrapper.APPLICATION_ID -> {
|
||||
context.toast(msg = "AppErrorsTracking has crashed, please see the log in console")
|
||||
loggerE(msg = "AppErrorsTracking has crashed itself, please see the Android Runtime Exception in console")
|
||||
}
|
||||
ConfigData.isEnableAppConfigTemplate -> when {
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.GLOBAL, packageName) -> when {
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.DIALOG) -> showAppErrorsWithDialog()
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTIFY) -> showAppErrorsWithNotify()
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.TOAST) -> showAppErrorsWithToast()
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTHING) -> {}
|
||||
}
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.DIALOG, packageName) -> showAppErrorsWithDialog()
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTIFY, packageName) -> showAppErrorsWithNotify()
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.TOAST, packageName) -> showAppErrorsWithToast()
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTHING, packageName) -> {}
|
||||
}
|
||||
else -> showAppErrorsWithDialog()
|
||||
}
|
||||
/** 打印错误日志 */
|
||||
if (isActualApp) loggerE(
|
||||
msg = "Application \"$packageName\" ${if (isRepeatingCrash) "keeps stopping" else "has stopped"}" +
|
||||
(if (packageName != processName) " --process \"$processName\"" else "") +
|
||||
"${if (userId != 0) " --user $userId" else ""} --pid $pid"
|
||||
) else loggerE(msg = "Process \"$processName\" ${if (isRepeatingCrash) "keeps stopping" else "has stopped"} --pid $pid")
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 APP 进程异常数据
|
||||
* @param context 当前实例
|
||||
* @param info 系统错误报告数据实例
|
||||
*/
|
||||
private fun AppErrorsProcessData.handleAppErrorsInfo(context: Context, info: ApplicationErrorReport.CrashInfo?) {
|
||||
AppErrorsRecordData.add(AppErrorsInfoBean.clone(context, pid, userId, appInfo?.packageName, info))
|
||||
loggerI(msg = "Received crash application data${if (userId != 0) " --user $userId" else ""} --pid $pid")
|
||||
}
|
||||
|
||||
override fun onHook() {
|
||||
/** 注册生命周期 */
|
||||
registerLifecycle()
|
||||
/** 干掉原生错误对话框 - 如果有 */
|
||||
ErrorDialogControllerClass.hook {
|
||||
injectMember {
|
||||
method {
|
||||
name = "hasCrashDialogs"
|
||||
emptyParam()
|
||||
}
|
||||
replaceToTrue()
|
||||
}
|
||||
injectMember {
|
||||
method {
|
||||
name = "showCrashDialogs"
|
||||
paramCount = 1
|
||||
}
|
||||
intercept()
|
||||
}
|
||||
}.ignoredHookClassNotFoundFailure()
|
||||
/** 干掉原生错误对话框 - API 30 以下 */
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
|
||||
ActivityTaskManagerService_LocalServiceClass.hook {
|
||||
injectMember {
|
||||
method {
|
||||
name = "canShowErrorDialogs"
|
||||
emptyParam()
|
||||
}
|
||||
replaceToFalse()
|
||||
}.ignoredNoSuchMemberFailure()
|
||||
}.ignoredHookClassNotFoundFailure()
|
||||
ActivityManagerServiceClass.hook {
|
||||
injectMember {
|
||||
method {
|
||||
name = "canShowErrorDialogs"
|
||||
emptyParam()
|
||||
}
|
||||
replaceToFalse()
|
||||
}.ignoredNoSuchMemberFailure()
|
||||
}.ignoredHookClassNotFoundFailure()
|
||||
}
|
||||
/** 干掉原生错误对话框 - 如果上述方法全部失效则直接结束对话框 */
|
||||
AppErrorDialogClass.hook {
|
||||
injectMember {
|
||||
method {
|
||||
name = "onCreate"
|
||||
param(BundleClass)
|
||||
}
|
||||
afterHook { instance<Dialog>().cancel() }
|
||||
}.ignoredNoSuchMemberFailure()
|
||||
injectMember {
|
||||
method {
|
||||
name = "onStart"
|
||||
emptyParam()
|
||||
}
|
||||
afterHook { instance<Dialog>().cancel() }
|
||||
}.ignoredNoSuchMemberFailure()
|
||||
}
|
||||
/** 注入自定义错误对话框 */
|
||||
AppErrorsClass.hook {
|
||||
injectMember {
|
||||
method {
|
||||
name = "handleShowAppErrorUi"
|
||||
param(MessageClass)
|
||||
}
|
||||
afterHook {
|
||||
/** 当前实例 */
|
||||
val context = appContext ?: field { name = "mContext" }.get(instance).cast<Context>() ?: return@afterHook
|
||||
|
||||
/** 当前错误数据 */
|
||||
val resultData = args().first().cast<Message>()?.obj
|
||||
|
||||
/** 当前进程信息 */
|
||||
val proc = AppErrorDialog_DataClass.toClass().field { name = "proc" }.get(resultData).any()
|
||||
/** 创建 APP 进程异常数据类 */
|
||||
AppErrorsProcessData(instance, proc, resultData).handleShowAppErrorUi(context)
|
||||
}
|
||||
}
|
||||
injectMember {
|
||||
method {
|
||||
name = "handleAppCrashInActivityController"
|
||||
returnType = BooleanType
|
||||
}
|
||||
afterHook {
|
||||
/** 当前实例 */
|
||||
val context = appContext ?: field { name = "mContext" }.get(instance).cast<Context>() ?: return@afterHook
|
||||
|
||||
/** 当前进程信息 */
|
||||
val proc = args().first().any() ?: return@afterHook loggerW(msg = "Received but got null ProcessRecord")
|
||||
/** 创建 APP 进程异常数据类 */
|
||||
AppErrorsProcessData(instance, proc).handleAppErrorsInfo(context, args(index = 1).cast())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/11.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.service
|
||||
|
||||
import android.service.quicksettings.TileService
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsRecordActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.navigate
|
||||
|
||||
class QuickStartTileService : TileService() {
|
||||
|
||||
override fun onClick() {
|
||||
super.onClick()
|
||||
/** 启动异常历史记录窗口 */
|
||||
navigate<AppErrorsRecordActivity>()
|
||||
}
|
||||
}
|
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/7.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.ui.activity.base
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.utils.factory.isNotSystemInDarkMode
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.highcapable.yukihookapi.hook.factory.current
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
|
||||
|
||||
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
|
||||
/** 获取绑定布局对象 */
|
||||
lateinit var binding: VB
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = current().generic()?.argument()?.method {
|
||||
name = "inflate"
|
||||
param(LayoutInflaterClass)
|
||||
}?.get()?.invoke<VB>(layoutInflater) ?: error("binding failed")
|
||||
setContentView(binding.root)
|
||||
/** 隐藏系统的标题栏 */
|
||||
supportActionBar?.hide()
|
||||
/** 初始化沉浸状态栏 */
|
||||
WindowCompat.getInsetsController(window, window.decorView).apply {
|
||||
isAppearanceLightStatusBars = isNotSystemInDarkMode
|
||||
isAppearanceLightNavigationBars = isNotSystemInDarkMode
|
||||
}
|
||||
ResourcesCompat.getColor(resources, R.color.colorThemeBackground, null).also {
|
||||
window?.statusBarColor = it
|
||||
window?.navigationBarColor = it
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) window?.navigationBarDividerColor = it
|
||||
}
|
||||
/** 装载子类 */
|
||||
onCreate()
|
||||
}
|
||||
|
||||
/** 回调 [onCreate] 方法 */
|
||||
abstract fun onCreate()
|
||||
|
||||
/**
|
||||
* 在旧版的 Android 系统中使用了 activity-alias 标签从启动器启动 Activity 会造成其组件名称 (完整类名) 为代理名称
|
||||
*
|
||||
* 为了获取真实的顶层 Activity 组件名称 (完整类名) - 如果名称不正确将自动执行一次结束并重新打开当前 Activity
|
||||
*/
|
||||
fun checkingTopComponentName() {
|
||||
/** 当前顶层的 Activity 组件名称 (完整类名) */
|
||||
val topComponentName = runCatching {
|
||||
@Suppress("DEPRECATION")
|
||||
(getSystemService(ACTIVITY_SERVICE) as? ActivityManager?)
|
||||
?.getRunningTasks(9999)?.firstOrNull()?.topActivity?.className ?: ""
|
||||
}.getOrNull() ?: ""
|
||||
if (topComponentName.isNotBlank() && topComponentName != javaClass.name) {
|
||||
finish()
|
||||
startActivity(Intent(this, javaClass))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出提示并退出
|
||||
* @param name 名称
|
||||
*/
|
||||
fun toastAndFinish(name: String) {
|
||||
toast(msg = "Invalid $name, exit")
|
||||
finish()
|
||||
}
|
||||
}
|
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/10/4.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
||||
|
||||
package com.fankes.apperrorstracking.ui.activity.debug
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.view.ContextMenu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.const.PackageName
|
||||
import com.fankes.apperrorstracking.databinding.ActivitiyLoggerBinding
|
||||
import com.fankes.apperrorstracking.databinding.AdapterLoggerBinding
|
||||
import com.fankes.apperrorstracking.databinding.DiaLoggerFilterBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.bindAdapter
|
||||
import com.fankes.apperrorstracking.utils.factory.copyToClipboard
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.factory.toUtcTime
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.highcapable.yukihookapi.hook.factory.dataChannel
|
||||
import com.highcapable.yukihookapi.hook.log.YukiHookLogger
|
||||
import com.highcapable.yukihookapi.hook.log.YukiLoggerData
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.PrintStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
class LoggerActivity : BaseActivity<ActivitiyLoggerBinding>() {
|
||||
|
||||
companion object {
|
||||
|
||||
/** 请求保存文件回调标识 */
|
||||
private const val WRITE_REQUEST_CODE = 0
|
||||
}
|
||||
|
||||
/** 回调适配器改变 */
|
||||
private var onChanged: (() -> Unit)? = null
|
||||
|
||||
/** 过滤条件 */
|
||||
private var filters = arrayListOf("D", "I", "W", "E")
|
||||
|
||||
/** 全部的调试日志数据 */
|
||||
private val listData = ArrayList<YukiLoggerData>()
|
||||
|
||||
override fun onCreate() {
|
||||
binding.titleBackIcon.setOnClickListener { finish() }
|
||||
binding.refreshIcon.setOnClickListener { refreshData() }
|
||||
binding.filterIcon.setOnClickListener {
|
||||
showDialog<DiaLoggerFilterBinding> {
|
||||
title = LocaleString.filterByCondition
|
||||
binding.configCheck0.isChecked = filters.any { it == "D" }
|
||||
binding.configCheck1.isChecked = filters.any { it == "I" }
|
||||
binding.configCheck2.isChecked = filters.any { it == "W" }
|
||||
binding.configCheck3.isChecked = filters.any { it == "E" }
|
||||
confirmButton {
|
||||
filters.clear()
|
||||
if (binding.configCheck0.isChecked) filters.add("D")
|
||||
if (binding.configCheck1.isChecked) filters.add("I")
|
||||
if (binding.configCheck2.isChecked) filters.add("W")
|
||||
if (binding.configCheck3.isChecked) filters.add("E")
|
||||
refreshData()
|
||||
}
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
binding.exportAllIcon.setOnClickListener {
|
||||
runCatching {
|
||||
startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "*/application"
|
||||
putExtra(Intent.EXTRA_TITLE, "app_errors_tracking_${System.currentTimeMillis().toUtcTime()}.log")
|
||||
}, WRITE_REQUEST_CODE)
|
||||
}.onFailure { toast(msg = "Start Android SAF failed") }
|
||||
}
|
||||
/** 设置列表元素和 Adapter */
|
||||
binding.listView.apply {
|
||||
bindAdapter {
|
||||
onBindDatas { listData }
|
||||
onBindViews<AdapterLoggerBinding> { binding, position ->
|
||||
listData[position].also { bean ->
|
||||
binding.priorityText.apply {
|
||||
text = bean.priority
|
||||
setBackgroundResource(
|
||||
when (bean.priority) {
|
||||
"D" -> R.drawable.bg_logger_d_round
|
||||
"I" -> R.drawable.bg_logger_i_round
|
||||
"W" -> R.drawable.bg_logger_w_round
|
||||
"E" -> R.drawable.bg_logger_e_round
|
||||
else -> R.drawable.bg_logger_d_round
|
||||
}
|
||||
)
|
||||
}
|
||||
binding.messageText.text = bean.msg.format()
|
||||
binding.timeText.text = bean.timestamp.format()
|
||||
binding.throwableText.isVisible = bean.throwable != null
|
||||
binding.throwableText.text = bean.throwable?.toStackTrace() ?: ""
|
||||
}
|
||||
}
|
||||
}.apply { onChanged = { notifyDataSetChanged() } }
|
||||
registerForContextMenu(this)
|
||||
}
|
||||
}
|
||||
|
||||
/** 更新列表数据 */
|
||||
private fun refreshData() {
|
||||
dataChannel(PackageName.SYSTEM_FRAMEWORK).obtainLoggerInMemoryData {
|
||||
listData.clear()
|
||||
it.takeIf { e -> e.isNotEmpty() }?.reversed()?.filter { filters.any { e -> it.priority == e } }?.forEach { e -> listData.add(e) }
|
||||
onChanged?.invoke()
|
||||
binding.listView.post { binding.listView.setSelection(0) }
|
||||
binding.exportAllIcon.isVisible = listData.isNotEmpty()
|
||||
binding.listView.isVisible = listData.isNotEmpty()
|
||||
binding.listNoDataView.isVisible = listData.isEmpty()
|
||||
binding.listNoDataView.text = if (filters.size < 4) LocaleString.noListResult else LocaleString.noListData
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化为本地时间格式
|
||||
* @return [String]
|
||||
*/
|
||||
private fun Long.format() = SimpleDateFormat.getDateTimeInstance().format(Date(this))
|
||||
|
||||
/**
|
||||
* 格式化消息字符串样式
|
||||
* @return [String]
|
||||
*/
|
||||
private fun String.format() = replace("--", "\n--")
|
||||
|
||||
/**
|
||||
* 获取完整的异常堆栈内容
|
||||
* @return [String]
|
||||
*/
|
||||
private fun Throwable.toStackTrace() = ByteArrayOutputStream().also { printStackTrace(PrintStream(it)) }.toString().trim()
|
||||
|
||||
override fun onCreateContextMenu(menu: ContextMenu?, v: View?, menuInfo: ContextMenu.ContextMenuInfo?) {
|
||||
menuInflater.inflate(R.menu.menu_logger_action, menu)
|
||||
super.onCreateContextMenu(menu, v, menuInfo)
|
||||
}
|
||||
|
||||
override fun onContextItemSelected(item: MenuItem): Boolean {
|
||||
if (item.menuInfo is AdapterView.AdapterContextMenuInfo)
|
||||
(item.menuInfo as? AdapterView.AdapterContextMenuInfo?)?.also {
|
||||
if (item.itemId == R.id.logger_copy)
|
||||
copyToClipboard(listData[it.position].let { e -> e.toString() + (e.throwable?.toStackTrace()?.let { t -> "\n$t" } ?: "") })
|
||||
}
|
||||
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(YukiHookLogger.contents(listData).toByteArray()) }?.close()
|
||||
toast(LocaleString.exportAllLogsSuccess)
|
||||
} ?: toast(LocaleString.exportAllLogsFail)
|
||||
}.onFailure { toast(LocaleString.exportAllLogsFail) }
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
/** 执行更新 */
|
||||
refreshData()
|
||||
}
|
||||
}
|
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/7.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
||||
|
||||
package com.fankes.apperrorstracking.ui.activity.errors
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
|
||||
import com.fankes.apperrorstracking.data.ConfigData
|
||||
import com.fankes.apperrorstracking.data.factory.bind
|
||||
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsDetailBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.appIconOf
|
||||
import com.fankes.apperrorstracking.utils.factory.appNameOf
|
||||
import com.fankes.apperrorstracking.utils.factory.copyToClipboard
|
||||
import com.fankes.apperrorstracking.utils.factory.dp
|
||||
import com.fankes.apperrorstracking.utils.factory.getSerializableExtraCompat
|
||||
import com.fankes.apperrorstracking.utils.factory.navigate
|
||||
import com.fankes.apperrorstracking.utils.factory.openSelfSetting
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.highcapable.yukihookapi.hook.log.loggerE
|
||||
|
||||
class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
|
||||
|
||||
companion object {
|
||||
|
||||
/** 请求保存文件回调标识 */
|
||||
private const val WRITE_REQUEST_CODE = 0
|
||||
|
||||
/** [AppErrorsInfoBean] 传值 */
|
||||
private const val EXTRA_APP_ERRORS_INFO = "app_errors_info_extra"
|
||||
|
||||
/**
|
||||
* 启动 [AppErrorsDetailActivity]
|
||||
* @param context 实例
|
||||
* @param appErrorsInfo 应用异常信息
|
||||
*/
|
||||
fun start(context: Context, appErrorsInfo: AppErrorsInfoBean) =
|
||||
context.navigate<AppErrorsDetailActivity> { putExtra(EXTRA_APP_ERRORS_INFO, appErrorsInfo) }
|
||||
}
|
||||
|
||||
/** 预导出的异常堆栈 */
|
||||
private var stackTrace = ""
|
||||
|
||||
override fun onCreate() {
|
||||
if (initUi(intent).not()) return
|
||||
binding.titleBackIcon.setOnClickListener { onBackPressed() }
|
||||
binding.disableAutoWrapErrorStackTraceSwitch.bind(ConfigData.DISABLE_AUTO_WRAP_ERROR_STACK_TRACE) {
|
||||
onInitialize {
|
||||
binding.errorStackTraceScrollView.isVisible = it
|
||||
binding.errorStackTraceFixedText.isGone = it
|
||||
}
|
||||
onChanged {
|
||||
reinitialize()
|
||||
resetScrollView()
|
||||
}
|
||||
}
|
||||
binding.detailTitleText.setOnClickListener { binding.appPanelScrollView.smoothScrollTo(0, 0) }
|
||||
resetScrollView()
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 [Intent] 中解析 [AppErrorsInfoBean] 并加载至界面
|
||||
*
|
||||
* @param intent 用于获取并解析 [AppErrorsInfoBean] 的 [Intent] 实例
|
||||
* @return [Boolean] 是否解析成功:true 为成功;false 为失败,可能是 [Intent] 为空或者 [AppErrorsInfoBean] 为空
|
||||
*/
|
||||
private fun initUi(intent: Intent?): Boolean {
|
||||
val appErrorsInfo = runCatching { intent?.getSerializableExtraCompat<AppErrorsInfoBean>(EXTRA_APP_ERRORS_INFO) }.getOrNull()
|
||||
if (appErrorsInfo == null) {
|
||||
toastAndFinish(name = "AppErrorsInfo")
|
||||
return false
|
||||
}
|
||||
if (appErrorsInfo.isEmpty) {
|
||||
binding.appPanelScrollView.isVisible = false
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.unableGetAppErrorsRecordTip
|
||||
confirmButton(LocaleString.gotIt) {
|
||||
cancel()
|
||||
finish()
|
||||
}
|
||||
cancelButton(LocaleString.goItNow) {
|
||||
cancel()
|
||||
finish()
|
||||
navigate<AppErrorsRecordActivity>()
|
||||
}
|
||||
noCancelable()
|
||||
}
|
||||
return false
|
||||
}
|
||||
binding.appInfoItem.setOnClickListener { openSelfSetting(appErrorsInfo.packageName) }
|
||||
binding.printIcon.setOnClickListener {
|
||||
loggerE(msg = appErrorsInfo.stackTrace)
|
||||
toast(LocaleString.printToLogcatSuccess)
|
||||
}
|
||||
binding.copyIcon.setOnClickListener { copyToClipboard(appErrorsInfo.stackOutputShareContent) }
|
||||
binding.exportIcon.setOnClickListener {
|
||||
stackTrace = appErrorsInfo.stackOutputFileContent
|
||||
runCatching {
|
||||
startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "*/application"
|
||||
putExtra(Intent.EXTRA_TITLE, "${appErrorsInfo.packageName}_${appErrorsInfo.utcTime}.log")
|
||||
}, WRITE_REQUEST_CODE)
|
||||
}.onFailure { toast(msg = "Start Android SAF failed") }
|
||||
}
|
||||
binding.shareIcon.setOnClickListener {
|
||||
startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).apply {
|
||||
type = "text/plain"
|
||||
putExtra(Intent.EXTRA_TEXT, appErrorsInfo.stackOutputShareContent)
|
||||
}, LocaleString.shareErrorStack))
|
||||
}
|
||||
binding.appIcon.setImageDrawable(appIconOf(appErrorsInfo.packageName))
|
||||
binding.appNameText.text = appNameOf(appErrorsInfo.packageName).ifBlank { appErrorsInfo.packageName }
|
||||
binding.appVersionText.text = appErrorsInfo.versionBrand
|
||||
binding.appUserIdText.isVisible = appErrorsInfo.userId > 0
|
||||
binding.appUserIdText.text = LocaleString.userId(appErrorsInfo.userId)
|
||||
binding.appCpuAbiText.text = appErrorsInfo.cpuAbi.ifBlank { LocaleString.noCpuAbi }
|
||||
binding.jvmErrorPanel.isGone = appErrorsInfo.isNativeCrash
|
||||
binding.errorTypeIcon.setImageResource(if (appErrorsInfo.isNativeCrash) R.drawable.ic_cpp else R.drawable.ic_java)
|
||||
binding.errorInfoText.text = appErrorsInfo.exceptionMessage
|
||||
binding.errorTypeText.text = appErrorsInfo.exceptionClassName
|
||||
binding.errorFileNameText.text = appErrorsInfo.throwFileName
|
||||
binding.errorThrowClassText.text = appErrorsInfo.throwClassName
|
||||
binding.errorThrowMethodText.text = appErrorsInfo.throwMethodName
|
||||
binding.errorLineNumberText.text = appErrorsInfo.throwLineNumber.toString()
|
||||
binding.errorRecordTimeText.text = appErrorsInfo.dateTime
|
||||
binding.errorStackTraceMovableText.text = appErrorsInfo.stackTrace
|
||||
binding.errorStackTraceFixedText.text = appErrorsInfo.stackTrace
|
||||
binding.appPanelScrollView.setOnScrollChangeListener { _, _, y, _, _ ->
|
||||
binding.detailTitleText.text = if (y >= 30.dp(context = this@AppErrorsDetailActivity))
|
||||
appNameOf(appErrorsInfo.packageName).ifBlank { appErrorsInfo.packageName }
|
||||
else LocaleString.appName
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** 修复在一些小屏设备上设置了 [TextView.setTextIsSelectable] 后布局自动上滑问题 */
|
||||
private fun resetScrollView() {
|
||||
binding.rootView.post {
|
||||
binding.appPanelScrollView.scrollTo(0, 0)
|
||||
binding.errorStackTraceScrollView.scrollTo(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
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(stackTrace.toByteArray()) }?.close()
|
||||
toast(LocaleString.outputStackSuccess)
|
||||
} ?: toast(LocaleString.outputStackFail)
|
||||
}.onFailure { toast(LocaleString.outputStackFail) }
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
intent?.removeExtra(EXTRA_APP_ERRORS_INFO)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
if (initUi(intent)) binding.appPanelScrollView.scrollTo(0, 0)
|
||||
}
|
||||
}
|
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/1.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.ui.activity.errors
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsDisplayBean
|
||||
import com.fankes.apperrorstracking.data.ConfigData
|
||||
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsDisplayBinding
|
||||
import com.fankes.apperrorstracking.databinding.DiaAppErrorsDisplayBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.colorOf
|
||||
import com.fankes.apperrorstracking.utils.factory.getSerializableExtraCompat
|
||||
import com.fankes.apperrorstracking.utils.factory.navigate
|
||||
import com.fankes.apperrorstracking.utils.factory.openSelfSetting
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
|
||||
class AppErrorsDisplayActivity : BaseActivity<ActivityAppErrorsDisplayBinding>() {
|
||||
|
||||
companion object {
|
||||
|
||||
/** 当前实例 - 单例运行 */
|
||||
private var instance: AppErrorsDisplayActivity? = null
|
||||
|
||||
/** [AppErrorsDisplayBean] 传值 */
|
||||
private const val EXTRA_APP_ERRORS_DISPLAY = "app_errors_display_extra"
|
||||
|
||||
/**
|
||||
* 启动 [AppErrorsDisplayActivity]
|
||||
* @param context 实例
|
||||
* @param appErrorsDisplay 应用异常信息显示
|
||||
*/
|
||||
fun start(context: Context, appErrorsDisplay: AppErrorsDisplayBean) =
|
||||
context.navigate<AppErrorsDisplayActivity>(isOutSide = true) { putExtra(EXTRA_APP_ERRORS_DISPLAY, appErrorsDisplay) }
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
instance?.finish()
|
||||
instance = this
|
||||
val appErrorsDisplay = runCatching { intent?.getSerializableExtraCompat<AppErrorsDisplayBean>(EXTRA_APP_ERRORS_DISPLAY) }.getOrNull()
|
||||
?: return toastAndFinish(name = "AppErrorsDisplay")
|
||||
/** 设置 Material 3 动态颜色主题 */
|
||||
if (ConfigData.isEnableMaterial3StyleAppErrorsDialog) setTheme(R.style.Theme_AppErrorsTracking_Translucent_Material3)
|
||||
/** 显示对话框 */
|
||||
showDialog<DiaAppErrorsDisplayBinding>(ConfigData.isEnableMaterial3StyleAppErrorsDialog.not()) {
|
||||
title = appErrorsDisplay.title
|
||||
if (ConfigData.isEnableMaterial3StyleAppErrorsDialog && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
arrayOf(
|
||||
binding.appInfoIcon, binding.closeAppIcon,
|
||||
binding.reopenAppIcon, binding.errorDetailIcon,
|
||||
binding.mutedIfUnlockIcon, binding.mutedIfRestartIcon
|
||||
).forEach { it.setColorFilter(resources.colorOf(android.R.color.system_accent1_600)) }
|
||||
binding.processNameText.isVisible = appErrorsDisplay.packageName != appErrorsDisplay.processName
|
||||
binding.appInfoItem.isVisible = appErrorsDisplay.isShowAppInfoButton
|
||||
binding.closeAppItem.isVisible = appErrorsDisplay.isShowReopenButton.not() && appErrorsDisplay.isShowCloseAppButton
|
||||
binding.reopenAppItem.isVisible = appErrorsDisplay.isShowReopenButton
|
||||
binding.processNameText.text = LocaleString.crashProcess(appErrorsDisplay.processName)
|
||||
binding.appInfoItem.setOnClickListener {
|
||||
cancel()
|
||||
openSelfSetting(appErrorsDisplay.packageName)
|
||||
}
|
||||
binding.closeAppItem.setOnClickListener { cancel() }
|
||||
binding.reopenAppItem.setOnClickListener {
|
||||
FrameworkTool.openAppUsedFramework(context, appErrorsDisplay.packageName, appErrorsDisplay.userId)
|
||||
cancel()
|
||||
}
|
||||
binding.errorDetailItem.setOnClickListener {
|
||||
FrameworkTool.fetchAppErrorInfoData(context, appErrorsDisplay.pid) { appErrorsInfo ->
|
||||
AppErrorsDetailActivity.start(context, appErrorsInfo)
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
binding.mutedIfUnlockItem.setOnClickListener {
|
||||
FrameworkTool.mutedErrorsIfUnlock(context, appErrorsDisplay.packageName) {
|
||||
toast(LocaleString.muteIfUnlockTip(appErrorsDisplay.appName))
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
binding.mutedIfRestartItem.setOnClickListener {
|
||||
FrameworkTool.mutedErrorsIfRestart(context, appErrorsDisplay.packageName) {
|
||||
toast(LocaleString.muteIfRestartTip(appErrorsDisplay.appName))
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
onCancel { finish() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
instance = null
|
||||
}
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/3.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.ui.activity.errors
|
||||
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.bean.MutedErrorsAppBean
|
||||
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsMutedBinding
|
||||
import com.fankes.apperrorstracking.databinding.AdapterAppErrorsMutedBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.appIconOf
|
||||
import com.fankes.apperrorstracking.utils.factory.appNameOf
|
||||
import com.fankes.apperrorstracking.utils.factory.bindAdapter
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
|
||||
class AppErrorsMutedActivity : BaseActivity<ActivityAppErrorsMutedBinding>() {
|
||||
|
||||
/** 回调适配器改变 */
|
||||
private var onChanged: (() -> Unit)? = null
|
||||
|
||||
/** 全部的已忽略异常的 APP 信息 */
|
||||
private val listData = ArrayList<MutedErrorsAppBean>()
|
||||
|
||||
override fun onCreate() {
|
||||
binding.titleBackIcon.setOnClickListener { onBackPressed() }
|
||||
binding.unmuteAllIcon.setOnClickListener {
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.areYouSureUnmuteAll
|
||||
confirmButton { FrameworkTool.unmuteAllErrorsApps(context) { refreshData() } }
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
/** 设置列表元素和 Adapter */
|
||||
binding.listView.bindAdapter {
|
||||
onBindDatas { listData }
|
||||
onBindViews<AdapterAppErrorsMutedBinding> { binding, position ->
|
||||
listData[position].also { bean ->
|
||||
binding.appIcon.setImageDrawable(appIconOf(bean.packageName))
|
||||
binding.appNameText.text = appNameOf(bean.packageName).ifBlank { bean.packageName }
|
||||
binding.muteTypeText.text = when (bean.type) {
|
||||
MutedErrorsAppBean.MuteType.UNTIL_UNLOCKS -> LocaleString.muteIfUnlock
|
||||
MutedErrorsAppBean.MuteType.UNTIL_REBOOTS -> LocaleString.muteIfRestart
|
||||
}
|
||||
binding.unmuteButton.setOnClickListener { FrameworkTool.unmuteErrorsApp(context, bean) { refreshData() } }
|
||||
}
|
||||
}
|
||||
}.apply { onChanged = { notifyDataSetChanged() } }
|
||||
}
|
||||
|
||||
/** 更新列表数据 */
|
||||
private fun refreshData() {
|
||||
FrameworkTool.fetchMutedErrorsAppsData(context = this) {
|
||||
listData.clear()
|
||||
it.takeIf { e -> e.isNotEmpty() }?.forEach { e -> listData.add(e) }
|
||||
onChanged?.invoke()
|
||||
binding.unmuteAllIcon.isVisible = listData.isNotEmpty()
|
||||
binding.listView.isVisible = listData.isNotEmpty()
|
||||
binding.listNoDataView.isVisible = listData.isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
/** 执行更新 */
|
||||
refreshData()
|
||||
}
|
||||
}
|
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/11.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION", "SetTextI18n")
|
||||
|
||||
package com.fankes.apperrorstracking.ui.activity.errors
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.view.ContextMenu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
|
||||
import com.fankes.apperrorstracking.bean.AppFiltersBean
|
||||
import com.fankes.apperrorstracking.bean.enum.AppFiltersType
|
||||
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsRecordBinding
|
||||
import com.fankes.apperrorstracking.databinding.AdapterAppErrorsRecordBinding
|
||||
import com.fankes.apperrorstracking.databinding.DiaAppErrorsStatisticsBinding
|
||||
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 com.fankes.apperrorstracking.utils.tool.ZipFileTool
|
||||
import com.fankes.apperrorstracking.wrapper.BuildConfigWrapper
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
|
||||
class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
|
||||
companion object {
|
||||
|
||||
/** 请求保存文件回调标识 */
|
||||
private const val WRITE_REQUEST_CODE = 0
|
||||
|
||||
/**
|
||||
* 获取 [Intent]
|
||||
* @return [Intent]
|
||||
*/
|
||||
fun intent() = Intent().apply { component = ComponentName(BuildConfigWrapper.APPLICATION_ID, AppErrorsRecordActivity::class.java.name) }
|
||||
}
|
||||
|
||||
/** 当前导出文件的路径 */
|
||||
private var outPutFilePath = ""
|
||||
|
||||
/** 回调适配器改变 */
|
||||
private var onChanged: (() -> Unit)? = null
|
||||
|
||||
/** 全部的 APP 异常信息 */
|
||||
private val listData = ArrayList<AppErrorsInfoBean>()
|
||||
|
||||
override fun onCreate() {
|
||||
binding.titleBackIcon.setOnClickListener { onBackPressed() }
|
||||
binding.appErrorSisIcon.setOnClickListener {
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
progressContent = LocaleString.generatingStatistics
|
||||
noCancelable()
|
||||
FrameworkTool.fetchAppListData(context, AppFiltersBean(type = AppFiltersType.ALL)) {
|
||||
newThread {
|
||||
val errorsApps = listData.groupBy { it.packageName }
|
||||
.map { it.key to it.value.size }
|
||||
.sortedByDescending { it.second }
|
||||
.takeIf { it.isNotEmpty() }
|
||||
val mostAppPackageName = errorsApps?.get(0)?.first ?: ""
|
||||
val mostErrorsType = listData.groupBy { it.exceptionClassName }
|
||||
.map { it.key to it.value.size }
|
||||
.sortedByDescending { it.second }
|
||||
.takeIf { it.isNotEmpty() }?.get(0)?.first?.simpleThwName() ?: ""
|
||||
val pptCount = (((errorsApps?.size?.toFloat() ?: 0f) * 100f) / it.size.toFloat()).decimal()
|
||||
runOnUiThread {
|
||||
cancel()
|
||||
showDialog<DiaAppErrorsStatisticsBinding> {
|
||||
title = LocaleString.appErrorsStatistics
|
||||
binding.totalErrorsUnitText.text = LocaleString.totalErrorsUnit(listData.size)
|
||||
binding.totalAppsUnitText.text = LocaleString.totalAppsUnit(it.size)
|
||||
binding.mostErrorsAppIcon.setImageDrawable(appIconOf(mostAppPackageName))
|
||||
binding.mostErrorsAppText.text = appNameOf(mostAppPackageName).ifBlank { mostAppPackageName }
|
||||
binding.mostErrorsTypeText.text = mostErrorsType
|
||||
binding.totalPptOfErrorsText.text = "$pptCount%"
|
||||
confirmButton(LocaleString.gotIt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.clearAllIcon.setOnClickListener {
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.areYouSureClearErrors
|
||||
confirmButton {
|
||||
FrameworkTool.clearAppErrorsInfoData(context) {
|
||||
refreshData()
|
||||
toast(LocaleString.allErrorsClearSuccess)
|
||||
}
|
||||
}
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
binding.exportAllIcon.setOnClickListener {
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.areYouSureExportAllErrors
|
||||
confirmButton { exportAll() }
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
/** 设置列表元素和 Adapter */
|
||||
binding.listView.apply {
|
||||
bindAdapter {
|
||||
onBindDatas { listData }
|
||||
onBindViews<AdapterAppErrorsRecordBinding> { binding, position ->
|
||||
listData[position].also { bean ->
|
||||
binding.appIcon.setImageDrawable(appIconOf(bean.packageName))
|
||||
binding.appNameText.text = appNameOf(bean.packageName).ifBlank { bean.packageName }
|
||||
binding.appUserIdText.isVisible = bean.userId > 0
|
||||
binding.appUserIdText.text = LocaleString.userId(bean.userId)
|
||||
binding.errorsTimeText.text = bean.crossTime
|
||||
binding.errorTypeIcon.setImageResource(if (bean.isNativeCrash) R.drawable.ic_cpp else R.drawable.ic_java)
|
||||
binding.errorTypeText.text = if (bean.isNativeCrash) "Native crash" else bean.exceptionClassName.simpleThwName()
|
||||
binding.errorMsgText.text = bean.exceptionMessage
|
||||
}
|
||||
}
|
||||
}.apply { onChanged = { notifyDataSetChanged() } }
|
||||
registerForContextMenu(this)
|
||||
setOnItemClickListener { _, _, p, _ -> AppErrorsDetailActivity.start(context, listData[p]) }
|
||||
}
|
||||
}
|
||||
|
||||
/** 更新列表数据 */
|
||||
private fun refreshData() {
|
||||
FrameworkTool.fetchAppErrorsInfoData(context = this) {
|
||||
binding.titleCountText.text = LocaleString.recordCount(it.size)
|
||||
binding.listProgressView.isVisible = false
|
||||
binding.appErrorSisIcon.isVisible = it.size >= 5
|
||||
binding.clearAllIcon.isVisible = it.isNotEmpty()
|
||||
binding.exportAllIcon.isVisible = it.isNotEmpty()
|
||||
binding.listView.isVisible = it.isNotEmpty()
|
||||
binding.listNoDataView.isVisible = it.isEmpty()
|
||||
listData.clear()
|
||||
it.takeIf { e -> e.isNotEmpty() }?.forEach { e -> listData.add(e) }
|
||||
onChanged?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
/** 打包导出全部 */
|
||||
private fun exportAll() {
|
||||
clearAllExportTemp()
|
||||
("${cacheDir.absolutePath}/temp").also { path ->
|
||||
File(path).mkdirs()
|
||||
listData.takeIf { it.isNotEmpty() }?.forEach {
|
||||
File("$path/${it.packageName}_${it.utcTime}.log").writeText(it.stackOutputFileContent)
|
||||
}
|
||||
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().toUtcTime()}.zip")
|
||||
}, WRITE_REQUEST_CODE)
|
||||
}.onFailure { toast(msg = "Start Android SAF failed") }
|
||||
}
|
||||
}
|
||||
|
||||
/** 清空导出的临时文件 */
|
||||
private fun clearAllExportTemp() {
|
||||
cacheDir.deleteRecursively()
|
||||
cacheDir.mkdirs()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取异常的精简名称
|
||||
* @return [String]
|
||||
*/
|
||||
private fun String.simpleThwName() =
|
||||
let { text -> if (text.contains(".")) text.split(".").let { e -> e[e.lastIndex] } else text }
|
||||
|
||||
override fun onCreateContextMenu(menu: ContextMenu?, v: View?, menuInfo: ContextMenu.ContextMenuInfo?) {
|
||||
menuInflater.inflate(R.menu.menu_list_detail_action, menu)
|
||||
super.onCreateContextMenu(menu, v, menuInfo)
|
||||
}
|
||||
|
||||
override fun onContextItemSelected(item: MenuItem): Boolean {
|
||||
if (item.menuInfo is AdapterContextMenuInfo)
|
||||
(item.menuInfo as? AdapterContextMenuInfo?)?.also {
|
||||
when (item.itemId) {
|
||||
R.id.aerrors_view_detail -> AppErrorsDetailActivity.start(context = this, listData[it.position])
|
||||
R.id.aerrors_app_info -> openSelfSetting(listData[it.position].packageName)
|
||||
R.id.aerrors_remove_record ->
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.areYouSureRemoveRecord
|
||||
confirmButton { FrameworkTool.removeAppErrorsInfoData(context, listData[it.position]) { refreshData() } }
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
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()
|
||||
/** 执行更新 */
|
||||
refreshData()
|
||||
}
|
||||
}
|
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/4.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.ui.activity.main
|
||||
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.bean.AppFiltersBean
|
||||
import com.fankes.apperrorstracking.bean.AppInfoBean
|
||||
import com.fankes.apperrorstracking.bean.enum.AppFiltersType
|
||||
import com.fankes.apperrorstracking.data.AppErrorsConfigData
|
||||
import com.fankes.apperrorstracking.data.enum.AppErrorsConfigType
|
||||
import com.fankes.apperrorstracking.databinding.ActivityConfigBinding
|
||||
import com.fankes.apperrorstracking.databinding.AdapterAppInfoBinding
|
||||
import com.fankes.apperrorstracking.databinding.DiaAppConfigBinding
|
||||
import com.fankes.apperrorstracking.databinding.DiaAppsFilterBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.appIconOf
|
||||
import com.fankes.apperrorstracking.utils.factory.bindAdapter
|
||||
import com.fankes.apperrorstracking.utils.factory.newThread
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
|
||||
class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
|
||||
|
||||
/** 过滤条件 */
|
||||
private var appFilters = AppFiltersBean()
|
||||
|
||||
/** 回调适配器改变 */
|
||||
private var onChanged: (() -> Unit)? = null
|
||||
|
||||
/** 全部的 APP 信息 */
|
||||
private val listData = ArrayList<AppInfoBean>()
|
||||
|
||||
override fun onCreate() {
|
||||
binding.titleBackIcon.setOnClickListener { finish() }
|
||||
binding.globalIcon.setOnClickListener {
|
||||
showAppConfigDialog(LocaleString.globalConfig, isShowGlobalConfig = false) { type ->
|
||||
AppErrorsConfigData.putAppShowingType(type)
|
||||
onChanged?.invoke()
|
||||
}
|
||||
}
|
||||
binding.batchIcon.setOnClickListener {
|
||||
showAppConfigDialog(LocaleString.batchOperationsNumber(listData.size), isNotSetDefaultValue = true) { type ->
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.areYouSureApplySiteApps(listData.size)
|
||||
confirmButton {
|
||||
listData.takeIf { it.isNotEmpty() }?.forEach { AppErrorsConfigData.putAppShowingType(type, it.packageName) }
|
||||
onChanged?.invoke()
|
||||
}
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.filterIcon.setOnClickListener {
|
||||
showDialog<DiaAppsFilterBinding> {
|
||||
title = LocaleString.filterByCondition
|
||||
binding.filtersRadioUser.isChecked = appFilters.type == AppFiltersType.USER
|
||||
binding.filtersRadioSystem.isChecked = appFilters.type == AppFiltersType.SYSTEM
|
||||
binding.filtersRadioAll.isChecked = appFilters.type == AppFiltersType.ALL
|
||||
binding.appFiltersEdit.apply {
|
||||
requestFocus()
|
||||
invalidate()
|
||||
if (appFilters.name.isNotBlank()) {
|
||||
setText(appFilters.name)
|
||||
setSelection(appFilters.name.length)
|
||||
}
|
||||
}
|
||||
/** 设置 [AppFiltersBean.type] */
|
||||
fun setAppFiltersType() {
|
||||
appFilters.type = when {
|
||||
binding.filtersRadioUser.isChecked -> AppFiltersType.USER
|
||||
binding.filtersRadioSystem.isChecked -> AppFiltersType.SYSTEM
|
||||
binding.filtersRadioAll.isChecked -> AppFiltersType.ALL
|
||||
else -> error("Invalid app filters type")
|
||||
}
|
||||
}
|
||||
confirmButton {
|
||||
setAppFiltersType()
|
||||
appFilters.name = binding.appFiltersEdit.text.toString().trim()
|
||||
refreshData()
|
||||
}
|
||||
cancelButton()
|
||||
if (appFilters.name.isNotBlank())
|
||||
neutralButton(LocaleString.clearFilters) {
|
||||
setAppFiltersType()
|
||||
appFilters.name = ""
|
||||
refreshData()
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.listView.apply {
|
||||
bindAdapter {
|
||||
onBindDatas { listData }
|
||||
onBindViews<AdapterAppInfoBinding> { binding, position ->
|
||||
listData[position].also { bean ->
|
||||
binding.appIcon.setImageDrawable(bean.icon)
|
||||
binding.appNameText.text = bean.name
|
||||
binding.configTypeText.text = when {
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.GLOBAL, bean.packageName) -> LocaleString.followGlobalConfig
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.DIALOG, bean.packageName) -> LocaleString.showErrorsDialog
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTIFY, bean.packageName) -> LocaleString.showErrorsNotify
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.TOAST, bean.packageName) -> LocaleString.showErrorsToast
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTHING, bean.packageName) -> LocaleString.showNothing
|
||||
else -> "Unknown type"
|
||||
}
|
||||
}
|
||||
}
|
||||
}.apply { onChanged = { notifyDataSetChanged() } }
|
||||
setOnItemClickListener { _, _, p, _ ->
|
||||
listData[p].also { bean ->
|
||||
showAppConfigDialog(bean.name, bean.packageName) { type ->
|
||||
AppErrorsConfigData.putAppShowingType(type, bean.packageName)
|
||||
onChanged?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/** 模块未完全激活将显示警告 */
|
||||
if (MainActivity.isModuleValied.not())
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.moduleNotFullyActivatedTip
|
||||
confirmButton { FrameworkTool.restartSystem(context) }
|
||||
cancelButton()
|
||||
noCancelable()
|
||||
}
|
||||
/** 开始刷新数据 */
|
||||
refreshData()
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示应用配置对话框
|
||||
* @param title 对话框标题
|
||||
* @param packageName APP 包名 - 默认空 (空时使用全局配置的默认值)
|
||||
* @param isNotSetDefaultValue 是否不设置选项的默认值 - 默认否
|
||||
* @param isShowGlobalConfig 是否显示跟随全局配置选项 - 默认是
|
||||
* @param result 回调类型结果
|
||||
*/
|
||||
private fun showAppConfigDialog(
|
||||
title: String,
|
||||
packageName: String = "",
|
||||
isNotSetDefaultValue: Boolean = false,
|
||||
isShowGlobalConfig: Boolean = true,
|
||||
result: (AppErrorsConfigType) -> Unit
|
||||
) {
|
||||
showDialog<DiaAppConfigBinding> {
|
||||
this.title = title
|
||||
binding.configRadio0.isVisible = isShowGlobalConfig
|
||||
if (isNotSetDefaultValue.not()) {
|
||||
if (isShowGlobalConfig) binding.configRadio0.isChecked =
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.GLOBAL, packageName)
|
||||
binding.configRadio1.isChecked = AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.DIALOG, packageName)
|
||||
binding.configRadio2.isChecked = AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTIFY, packageName)
|
||||
binding.configRadio3.isChecked = AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.TOAST, packageName)
|
||||
binding.configRadio4.isChecked = AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTHING, packageName)
|
||||
}
|
||||
confirmButton {
|
||||
result(
|
||||
when {
|
||||
binding.configRadio0.isChecked -> AppErrorsConfigType.GLOBAL
|
||||
binding.configRadio1.isChecked -> AppErrorsConfigType.DIALOG
|
||||
binding.configRadio2.isChecked -> AppErrorsConfigType.NOTIFY
|
||||
binding.configRadio3.isChecked -> AppErrorsConfigType.TOAST
|
||||
binding.configRadio4.isChecked -> AppErrorsConfigType.NOTHING
|
||||
else -> error("Invalid config type")
|
||||
}
|
||||
)
|
||||
FrameworkTool.refreshFrameworkPrefsData(context)
|
||||
}
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
|
||||
/** 刷新列表数据 */
|
||||
private fun refreshData() {
|
||||
binding.listProgressView.isVisible = true
|
||||
binding.globalIcon.isVisible = false
|
||||
binding.batchIcon.isVisible = false
|
||||
binding.filterIcon.isVisible = false
|
||||
binding.listView.isVisible = false
|
||||
binding.listNoDataView.isVisible = false
|
||||
binding.titleCountText.text = LocaleString.loading
|
||||
FrameworkTool.fetchAppListData(context = this, appFilters) {
|
||||
/** 设置一个临时变量用于更新列表数据 */
|
||||
val tempsData = ArrayList<AppInfoBean>()
|
||||
newThread {
|
||||
runCatching {
|
||||
it.takeIf { e -> e.isNotEmpty() }?.forEach { e ->
|
||||
tempsData.add(e)
|
||||
e.icon = appIconOf(e.packageName)
|
||||
}
|
||||
}
|
||||
if (isDestroyed.not()) runOnUiThread {
|
||||
listData.clear()
|
||||
listData.addAll(tempsData)
|
||||
onChanged?.invoke()
|
||||
binding.listView.post { binding.listView.setSelection(0) }
|
||||
binding.listProgressView.isVisible = false
|
||||
binding.globalIcon.isVisible = true
|
||||
binding.batchIcon.isVisible = listData.isNotEmpty()
|
||||
binding.filterIcon.isVisible = true
|
||||
binding.listView.isVisible = listData.isNotEmpty()
|
||||
binding.listNoDataView.isVisible = listData.isEmpty()
|
||||
binding.titleCountText.text = LocaleString.resultCount(listData.size)
|
||||
} else tempsData.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/14.
|
||||
*/
|
||||
@file:Suppress("SetTextI18n")
|
||||
|
||||
package com.fankes.apperrorstracking.ui.activity.main
|
||||
|
||||
import android.os.Build
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.const.ModuleVersion
|
||||
import com.fankes.apperrorstracking.data.ConfigData
|
||||
import com.fankes.apperrorstracking.data.factory.bind
|
||||
import com.fankes.apperrorstracking.databinding.ActivityMainBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.ui.activity.debug.LoggerActivity
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsMutedActivity
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsRecordActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.hideOrShowLauncherIcon
|
||||
import com.fankes.apperrorstracking.utils.factory.isLauncherIconShowing
|
||||
import com.fankes.apperrorstracking.utils.factory.isSystemLanguageSimplifiedChinese
|
||||
import com.fankes.apperrorstracking.utils.factory.navigate
|
||||
import com.fankes.apperrorstracking.utils.factory.openBrowser
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.fankes.apperrorstracking.utils.tool.AppAnalyticsTool
|
||||
import com.fankes.apperrorstracking.utils.tool.AppAnalyticsTool.bindAppAnalytics
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
import com.fankes.apperrorstracking.utils.tool.GithubReleaseTool
|
||||
import com.fankes.projectpromote.ProjectPromote
|
||||
import com.highcapable.yukihookapi.YukiHookAPI
|
||||
|
||||
class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
|
||||
companion object {
|
||||
|
||||
/** 系统版本 */
|
||||
private val systemVersion = "${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT}) ${Build.DISPLAY}"
|
||||
|
||||
/** 模块是否有效 */
|
||||
var isModuleValied = false
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
checkingTopComponentName()
|
||||
/** 检查更新 */
|
||||
GithubReleaseTool.checkingForUpdate(context = this, ModuleVersion.NAME) { version, function ->
|
||||
binding.mainTextReleaseVersion.apply {
|
||||
text = LocaleString.clickToUpdate(version)
|
||||
isVisible = true
|
||||
setOnClickListener { function() }
|
||||
}
|
||||
}
|
||||
/** 推广、恰饭 */
|
||||
if (YukiHookAPI.Status.isXposedModuleActive) ProjectPromote.show(activity = this, ModuleVersion.toString())
|
||||
/** 显示开发者提示 */
|
||||
if (ConfigData.isShowDeveloperNotice)
|
||||
showDialog {
|
||||
title = LocaleString.developerNotice
|
||||
msg = LocaleString.developerNoticeTip
|
||||
confirmButton(LocaleString.gotIt) { ConfigData.isShowDeveloperNotice = false }
|
||||
noCancelable()
|
||||
}
|
||||
/** 设置 CI 自动构建标识 */
|
||||
if (ModuleVersion.isCiMode)
|
||||
binding.mainTextReleaseVersion.apply {
|
||||
text = "CI ${ModuleVersion.GITHUB_COMMIT_ID}"
|
||||
isVisible = true
|
||||
setOnClickListener {
|
||||
showDialog {
|
||||
title = LocaleString.ciNoticeDialogTitle
|
||||
msg = LocaleString.ciNoticeDialogContent(ModuleVersion.GITHUB_COMMIT_ID)
|
||||
confirmButton(LocaleString.gotIt)
|
||||
noCancelable()
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.mainTextVersion.text = LocaleString.moduleVersion(ModuleVersion.NAME)
|
||||
binding.mainTextSystemVersion.text = LocaleString.systemVersion(systemVersion)
|
||||
binding.onlyShowErrorsInFrontSwitch.bind(ConfigData.ENABLE_ONLY_SHOW_ERRORS_IN_FRONT)
|
||||
binding.onlyShowErrorsInMainProcessSwitch.bind(ConfigData.ENABLE_ONLY_SHOW_ERRORS_IN_MAIN)
|
||||
binding.alwaysShowsReopenAppOptionsSwitch.bind(ConfigData.ENABLE_ALWAYS_SHOWS_REOPEN_APP_OPTIONS)
|
||||
binding.enableAppsConfigsTemplateSwitch.bind(ConfigData.ENABLE_APP_CONFIG_TEMPLATE) {
|
||||
onInitialize { binding.mgrAppsConfigsTemplateButton.isVisible = it }
|
||||
onChanged { reinitialize() }
|
||||
}
|
||||
binding.enableMaterial3AppErrorsDialogSwitch.bind(ConfigData.ENABLE_MATERIAL3_STYLE_APP_ERRORS_DIALOG)
|
||||
/** 设置匿名统计 */
|
||||
binding.appAnalyticsConfigItem.isVisible = AppAnalyticsTool.isAvailable
|
||||
binding.enableAnonymousStatisticsSwitch.bindAppAnalytics()
|
||||
/** 系统版本点击事件 */
|
||||
binding.mainTextSystemVersion.setOnClickListener {
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = systemVersion
|
||||
confirmButton(LocaleString.gotIt)
|
||||
}
|
||||
}
|
||||
/** 管理应用配置模板按钮点击事件 */
|
||||
binding.mgrAppsConfigsTemplateButton.setOnClickListener { whenActivated { navigate<ConfigureActivity>() } }
|
||||
/** 功能管理按钮点击事件 */
|
||||
binding.viewErrorsRecordButton.setOnClickListener { whenActivated { navigate<AppErrorsRecordActivity>() } }
|
||||
binding.viewMutedErrorsAppsButton.setOnClickListener { whenActivated { navigate<AppErrorsMutedActivity>() } }
|
||||
/** 调试日志按钮点击事件 */
|
||||
binding.titleLoggerIcon.setOnClickListener { navigate<LoggerActivity>() }
|
||||
/** 重启按钮点击事件 */
|
||||
binding.titleRestartIcon.setOnClickListener { FrameworkTool.restartSystem(context = this) }
|
||||
/** 项目地址按钮点击事件 */
|
||||
binding.titleGithubIcon.setOnClickListener { openBrowser(url = "https://github.com/KitsunePie/AppErrorsTracking") }
|
||||
/** 恰饭! */
|
||||
binding.paymentFollowingZhCnItem.isVisible = isSystemLanguageSimplifiedChinese
|
||||
binding.linkWithFollowMe.setOnClickListener {
|
||||
openBrowser(url = "https://www.coolapk.com/u/876977", packageName = "com.coolapk.market")
|
||||
}
|
||||
/** 设置桌面图标显示隐藏 */
|
||||
binding.hideIconInLauncherSwitch.isChecked = isLauncherIconShowing.not()
|
||||
binding.hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (btn.isPressed.not()) return@setOnCheckedChangeListener
|
||||
hideOrShowLauncherIcon(b)
|
||||
}
|
||||
}
|
||||
|
||||
/** 刷新模块状态 */
|
||||
private fun refreshModuleStatus() {
|
||||
binding.mainLinStatus.setBackgroundResource(
|
||||
when {
|
||||
YukiHookAPI.Status.isXposedModuleActive && isModuleValied.not() -> R.drawable.bg_yellow_round
|
||||
YukiHookAPI.Status.isXposedModuleActive -> R.drawable.bg_green_round
|
||||
else -> R.drawable.bg_dark_round
|
||||
}
|
||||
)
|
||||
binding.mainImgStatus.setImageResource(
|
||||
when {
|
||||
YukiHookAPI.Status.isXposedModuleActive -> R.drawable.ic_success
|
||||
else -> R.drawable.ic_warn
|
||||
}
|
||||
)
|
||||
binding.mainTextStatus.text = when {
|
||||
YukiHookAPI.Status.isXposedModuleActive && isModuleValied.not() -> LocaleString.moduleNotFullyActivated
|
||||
YukiHookAPI.Status.isXposedModuleActive -> LocaleString.moduleIsActivated
|
||||
else -> LocaleString.moduleNotActivated
|
||||
}
|
||||
binding.mainTextApiWay.isVisible = YukiHookAPI.Status.isXposedModuleActive
|
||||
binding.mainTextApiWay.text = "Activated by ${YukiHookAPI.Status.Executor.name} API ${YukiHookAPI.Status.Executor.apiLevel}"
|
||||
}
|
||||
|
||||
/**
|
||||
* 当模块激活后才能执行相应功能
|
||||
* @param callback 激活后回调
|
||||
*/
|
||||
private inline fun whenActivated(callback: () -> Unit) {
|
||||
if (YukiHookAPI.Status.isXposedModuleActive) callback() else toast(LocaleString.moduleNotActivated)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
/** 刷新模块状态 */
|
||||
refreshModuleStatus()
|
||||
/** 检查模块激活状态 */
|
||||
FrameworkTool.checkingActivated(context = this) { isValied ->
|
||||
isModuleValied = isValied
|
||||
refreshModuleStatus()
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/1.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.ui.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.widget.LinearLayout
|
||||
import com.fankes.apperrorstracking.utils.factory.dp
|
||||
import top.defaults.drawabletoolbox.DrawableBuilder
|
||||
|
||||
class ItemLinearLayout(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
|
||||
|
||||
init {
|
||||
gravity = Gravity.CENTER or Gravity.START
|
||||
background = DrawableBuilder()
|
||||
.rounded()
|
||||
.cornerRadius(15.dp(context))
|
||||
.ripple()
|
||||
.rippleColor(0xFFAAAAAA.toInt())
|
||||
.build()
|
||||
}
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/14.
|
||||
*/
|
||||
@file:Suppress("SameParameterValue")
|
||||
|
||||
package com.fankes.apperrorstracking.ui.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.text.TextUtils
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.SwitchCompat
|
||||
import com.fankes.apperrorstracking.utils.factory.dp
|
||||
import com.fankes.apperrorstracking.utils.factory.isSystemInDarkMode
|
||||
import top.defaults.drawabletoolbox.DrawableBuilder
|
||||
|
||||
class MaterialSwitch(context: Context, attrs: AttributeSet?) : SwitchCompat(context, attrs) {
|
||||
|
||||
private fun toColors(selected: Int, pressed: Int, normal: Int): ColorStateList {
|
||||
val colors = intArrayOf(selected, pressed, normal)
|
||||
val states = arrayOfNulls<IntArray>(3)
|
||||
states[0] = intArrayOf(android.R.attr.state_checked)
|
||||
states[1] = intArrayOf(android.R.attr.state_pressed)
|
||||
states[2] = intArrayOf()
|
||||
return ColorStateList(states, colors)
|
||||
}
|
||||
|
||||
private val thumbColor get() = if (context.isSystemInDarkMode) 0xFF7C7C7C else 0xFFCCCCCC
|
||||
|
||||
init {
|
||||
trackDrawable = DrawableBuilder()
|
||||
.rectangle()
|
||||
.rounded()
|
||||
.solidColor(0xFF656565.toInt())
|
||||
.height(20.dp(context))
|
||||
.cornerRadius(15.dp(context))
|
||||
.build()
|
||||
thumbDrawable = DrawableBuilder()
|
||||
.rectangle()
|
||||
.rounded()
|
||||
.solidColor(Color.WHITE)
|
||||
.size(20.dp(context), 20.dp(context))
|
||||
.cornerRadius(20.dp(context))
|
||||
.strokeWidth(8.dp(context))
|
||||
.strokeColor(Color.TRANSPARENT)
|
||||
.build()
|
||||
trackTintList = toColors(
|
||||
0xFF656565.toInt(),
|
||||
thumbColor.toInt(),
|
||||
thumbColor.toInt()
|
||||
)
|
||||
isSingleLine = true
|
||||
ellipsize = TextUtils.TruncateAt.END
|
||||
}
|
||||
}
|
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/3.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.BaseAdapter
|
||||
import android.widget.ListView
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
|
||||
|
||||
/**
|
||||
* 绑定 [BaseAdapter] 到 [ListView]
|
||||
* @param initiate 方法体
|
||||
* @return [BaseAdapter]
|
||||
*/
|
||||
inline fun ListView.bindAdapter(initiate: BaseAdapterCreater.() -> Unit) =
|
||||
BaseAdapterCreater(context).apply(initiate).baseAdapter?.apply { adapter = this } ?: error("BaseAdapter not binded")
|
||||
|
||||
/**
|
||||
* [BaseAdapter] 创建类
|
||||
* @param context 实例
|
||||
*/
|
||||
class BaseAdapterCreater(val context: Context) {
|
||||
|
||||
/** 当前 [List] 回调 */
|
||||
var listDataCallback: (() -> List<*>)? = null
|
||||
|
||||
/** 当前 [BaseAdapter] */
|
||||
var baseAdapter: BaseAdapter? = null
|
||||
|
||||
/**
|
||||
* 绑定 [List] 到 [ListView]
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun onBindDatas(result: (() -> List<*>)) {
|
||||
listDataCallback = result
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定 [BaseAdapter] 到 [ListView]
|
||||
* @param bindViews 回调 - ([VB] 每项,[Int] 下标)
|
||||
*/
|
||||
inline fun <reified VB : ViewBinding> onBindViews(crossinline bindViews: (binding: VB, position: Int) -> Unit) {
|
||||
baseAdapter = object : BaseAdapter() {
|
||||
override fun getCount() = listDataCallback?.let { it() }?.size ?: 0
|
||||
override fun getItem(position: Int) = listDataCallback?.let { it() }?.get(position)
|
||||
override fun getItemId(position: Int) = position.toLong()
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
|
||||
var holderView = convertView
|
||||
val holder: VB
|
||||
if (convertView == null) {
|
||||
holder = VB::class.java.method {
|
||||
name = "inflate"
|
||||
param(LayoutInflaterClass)
|
||||
}.get().invoke<VB>(LayoutInflater.from(context)) ?: error("ViewHolder binding failed")
|
||||
holderView = holder.root.apply { tag = holder }
|
||||
} else holder = convertView.tag as VB
|
||||
bindViews(holder, position)
|
||||
return holderView ?: error("ViewHolder binding failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/12.
|
||||
*/
|
||||
@file:Suppress("unused", "OPT_IN_USAGE", "EXPERIMENTAL_API_USAGE")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import com.highcapable.yukihookapi.YukiHookAPI
|
||||
import com.highcapable.yukihookapi.annotation.CauseProblemsApi
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
|
||||
|
||||
/**
|
||||
* 构造 [VB] 自定义 View 对话框
|
||||
* @param isDisableMaterial3 是否禁用 Material 3 风格对话框 - 默认否
|
||||
* @param initiate 对话框方法体
|
||||
*/
|
||||
@JvmName(name = "showDialog_Generics")
|
||||
inline fun <reified VB : ViewBinding> Context.showDialog(isDisableMaterial3: Boolean = false, initiate: DialogBuilder<VB>.() -> Unit) =
|
||||
DialogBuilder<VB>(context = this, isDisableMaterial3, VB::class.java).apply(initiate).show()
|
||||
|
||||
/**
|
||||
* 构造对话框
|
||||
* @param initiate 对话框方法体
|
||||
* @param isDisableMaterial3 是否禁用 Material 3 风格对话框 - 默认否
|
||||
*/
|
||||
inline fun Context.showDialog(isDisableMaterial3: Boolean = false, initiate: DialogBuilder<*>.() -> Unit) =
|
||||
DialogBuilder<ViewBinding>(context = this, isDisableMaterial3).apply(initiate).show()
|
||||
|
||||
/**
|
||||
* 对话框构造器
|
||||
* @param context 实例
|
||||
* @param isDisableMaterial3 是否禁用 Material 3 风格对话框 - 默认否
|
||||
* @param bindingClass [ViewBinding] 的 [Class] 实例 or null
|
||||
*/
|
||||
class DialogBuilder<VB : ViewBinding>(
|
||||
val context: Context,
|
||||
private val isDisableMaterial3: Boolean = false,
|
||||
private val bindingClass: Class<*>? = null
|
||||
) {
|
||||
|
||||
/** 实例对象 */
|
||||
private var instance: AlertDialog.Builder? = null
|
||||
|
||||
/** 对话框取消监听 */
|
||||
private var onCancel: (() -> Unit)? = null
|
||||
|
||||
/** 对话框实例 */
|
||||
private var dialogInstance: Dialog? = null
|
||||
|
||||
/** 自定义布局 */
|
||||
private var customLayoutView: View? = null
|
||||
|
||||
/**
|
||||
* 获取 [DialogBuilder] 绑定布局对象
|
||||
* @return [VB]
|
||||
*/
|
||||
val binding by lazy {
|
||||
bindingClass?.method {
|
||||
name = "inflate"
|
||||
param(LayoutInflaterClass)
|
||||
}?.get()?.invoke<VB>(LayoutInflater.from(context))?.apply {
|
||||
customLayoutView = root
|
||||
} ?: error("This dialog maybe not a custom view dialog")
|
||||
}
|
||||
|
||||
init {
|
||||
if (YukiHookAPI.Status.isXposedEnvironment) error("This dialog is not allowed to created in Xposed environment")
|
||||
instance = MaterialAlertDialogBuilder(context).also { builder ->
|
||||
if (isDisableMaterial3)
|
||||
builder.background = (builder.background as? MaterialShapeDrawable)?.apply { setCornerSize(15.dpFloat(context)) }
|
||||
}
|
||||
}
|
||||
|
||||
/** 设置对话框不可关闭 */
|
||||
fun noCancelable() {
|
||||
instance?.setCancelable(false)
|
||||
}
|
||||
|
||||
/** 设置对话框标题 */
|
||||
var title
|
||||
get() = ""
|
||||
set(value) {
|
||||
instance?.setTitle(value)
|
||||
}
|
||||
|
||||
/** 设置对话框消息内容 */
|
||||
var msg
|
||||
get() = ""
|
||||
set(value) {
|
||||
instance?.setMessage(value)
|
||||
}
|
||||
|
||||
/** 设置进度条对话框消息内容 */
|
||||
var progressContent
|
||||
get() = ""
|
||||
set(value) {
|
||||
if (customLayoutView == null)
|
||||
customLayoutView = LinearLayout(context).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
gravity = Gravity.CENTER or Gravity.START
|
||||
addView(CircularProgressIndicator(context).apply {
|
||||
isIndeterminate = true
|
||||
trackCornerRadius = 10.dp(context)
|
||||
})
|
||||
addView(View(context).apply { layoutParams = ViewGroup.LayoutParams(20.dp(context), 5) })
|
||||
addView(TextView(context).apply {
|
||||
tag = "progressContent"
|
||||
text = value
|
||||
})
|
||||
setPadding(20.dp(context), 20.dp(context), 20.dp(context), 20.dp(context))
|
||||
}
|
||||
else customLayoutView?.findViewWithTag<TextView>("progressContent")?.text = value
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对话框确定按钮
|
||||
* @param text 按钮文本内容
|
||||
* @param callback 点击事件
|
||||
*/
|
||||
fun confirmButton(text: String = LocaleString.confirm, callback: () -> Unit = {}) {
|
||||
instance?.setPositiveButton(text) { _, _ -> callback() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对话框取消按钮
|
||||
* @param text 按钮文本内容
|
||||
* @param callback 点击事件
|
||||
*/
|
||||
fun cancelButton(text: String = LocaleString.cancel, callback: () -> Unit = {}) {
|
||||
instance?.setNegativeButton(text) { _, _ -> callback() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对话框第三个按钮
|
||||
* @param text 按钮文本内容
|
||||
* @param callback 点击事件
|
||||
*/
|
||||
fun neutralButton(text: String = LocaleString.more, callback: () -> Unit = {}) {
|
||||
instance?.setNeutralButton(text) { _, _ -> callback() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 当对话框关闭时
|
||||
* @param callback 回调
|
||||
*/
|
||||
fun onCancel(callback: () -> Unit) {
|
||||
onCancel = callback
|
||||
}
|
||||
|
||||
/** 取消对话框 */
|
||||
fun cancel() = dialogInstance?.cancel()
|
||||
|
||||
/** 显示对话框 */
|
||||
@CauseProblemsApi
|
||||
fun show() {
|
||||
/** 若当前自定义 View 的对话框没有调用 [binding] 将会对其手动调用一次以确保显示布局 */
|
||||
if (bindingClass != null) binding
|
||||
runCatching {
|
||||
instance?.create()?.apply {
|
||||
customLayoutView?.let { setView(it) }
|
||||
dialogInstance = this
|
||||
setOnCancelListener { onCancel?.invoke() }
|
||||
}?.show()
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/7.
|
||||
*/
|
||||
@file:Suppress("unused", "NotificationPermission")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.PackageInfoFlags
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.wrapper.BuildConfigWrapper
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.highcapable.yukihookapi.hook.factory.field
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.type.android.ApplicationInfoClass
|
||||
import com.highcapable.yukihookapi.hook.type.android.ContextClass
|
||||
import com.highcapable.yukihookapi.hook.type.android.IntentClass
|
||||
import com.highcapable.yukihookapi.hook.type.android.UserHandleClass
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import java.io.Serializable
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* 当前系统环境是否为简体中文
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val isSystemLanguageSimplifiedChinese
|
||||
get(): Boolean {
|
||||
val locale = Locale.getDefault()
|
||||
return locale.language == "zh" && locale.country == "CN"
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统深色模式是否开启
|
||||
* @return [Boolean] 是否开启
|
||||
*/
|
||||
val Context.isSystemInDarkMode get() = (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
||||
|
||||
/**
|
||||
* 系统深色模式是否没开启
|
||||
* @return [Boolean] 是否开启
|
||||
*/
|
||||
inline val Context.isNotSystemInDarkMode get() = !isSystemInDarkMode
|
||||
|
||||
/**
|
||||
* dp 转换为 pxInt
|
||||
* @param context 使用的实例
|
||||
* @return [Int]
|
||||
*/
|
||||
fun Number.dp(context: Context) = dpFloat(context).toInt()
|
||||
|
||||
/**
|
||||
* dp 转换为 pxFloat
|
||||
* @param context 使用的实例
|
||||
* @return [Float]
|
||||
*/
|
||||
fun Number.dpFloat(context: Context) = toFloat() * context.resources.displayMetrics.density
|
||||
|
||||
/**
|
||||
* 获取 [Drawable]
|
||||
* @param resId 属性资源 ID
|
||||
* @return [Drawable]
|
||||
*/
|
||||
fun Resources.drawableOf(@DrawableRes resId: Int) = ResourcesCompat.getDrawable(this, resId, null) ?: error("Invalid resources")
|
||||
|
||||
/**
|
||||
* 获取颜色
|
||||
* @param resId 属性资源 ID
|
||||
* @return [Int]
|
||||
*/
|
||||
fun Resources.colorOf(@ColorRes resId: Int) = ResourcesCompat.getColor(this, resId, null)
|
||||
|
||||
/**
|
||||
* 得到 APP 安装包信息 (兼容)
|
||||
* @param packageName APP 包名
|
||||
* @param flag [PackageInfoFlags]
|
||||
* @return [PackageInfo] or null
|
||||
*/
|
||||
private fun Context.getPackageInfoCompat(packageName: String, flag: Number = 0) = runCatching {
|
||||
@Suppress("DEPRECATION", "KotlinRedundantDiagnosticSuppress")
|
||||
if (Build.VERSION.SDK_INT >= 33)
|
||||
packageManager?.getPackageInfo(packageName, PackageInfoFlags.of(flag.toLong()))
|
||||
else packageManager?.getPackageInfo(packageName, flag.toInt())
|
||||
}.getOrNull()
|
||||
|
||||
/**
|
||||
* 得到 APP 版本号 (兼容 [PackageInfo.getLongVersionCode])
|
||||
* @return [Int]
|
||||
*/
|
||||
private val PackageInfo.versionCodeCompat get() = PackageInfoCompat.getLongVersionCode(this)
|
||||
|
||||
/**
|
||||
* 获取系统中全部已安装应用列表
|
||||
* @return [List]<[PackageInfo]>
|
||||
*/
|
||||
fun Context.listOfPackages() = runCatching {
|
||||
@Suppress("DEPRECATION", "KotlinRedundantDiagnosticSuppress")
|
||||
if (Build.VERSION.SDK_INT >= 33)
|
||||
packageManager?.getInstalledPackages(PackageInfoFlags.of(PackageManager.GET_CONFIGURATIONS.toLong()))
|
||||
else packageManager?.getInstalledPackages(PackageManager.GET_CONFIGURATIONS)
|
||||
}.getOrNull() ?: emptyList()
|
||||
|
||||
/**
|
||||
* 得到 APP 名称
|
||||
* @param packageName APP 包名 - 默认为当前 APP
|
||||
* @return [String] 无法获取时返回 ""
|
||||
*/
|
||||
fun Context.appNameOf(packageName: String = getPackageName()) =
|
||||
getPackageInfoCompat(packageName)?.applicationInfo?.loadLabel(packageManager)?.toString() ?: ""
|
||||
|
||||
/**
|
||||
* 得到 APP 版本信息与版本号
|
||||
* @param packageName APP 包名 - 默认为当前 APP
|
||||
* @return [String] 无法获取时返回 ""
|
||||
*/
|
||||
fun Context.appVersionBrandOf(packageName: String = getPackageName()) =
|
||||
if (appVersionNameOf(packageName).isNotBlank()) "${appVersionNameOf(packageName)}(${appVersionCodeOf(packageName)})" else ""
|
||||
|
||||
/**
|
||||
* 得到 APP 版本名称
|
||||
* @param packageName APP 包名 - 默认为当前 APP
|
||||
* @return [String] 无法获取时返回 ""
|
||||
*/
|
||||
fun Context.appVersionNameOf(packageName: String = getPackageName()) = getPackageInfoCompat(packageName)?.versionName ?: ""
|
||||
|
||||
/**
|
||||
* 得到 APP 版本号
|
||||
* @param packageName APP 包名 - 默认为当前 APP
|
||||
* @return [Long] 无法获取时返回 -1
|
||||
*/
|
||||
fun Context.appVersionCodeOf(packageName: String = getPackageName()) = getPackageInfoCompat(packageName)?.versionCodeCompat ?: -1L
|
||||
|
||||
/**
|
||||
* 获取 APP CPU ABI 名称
|
||||
* @param packageName APP 包名 - 默认为当前 APP
|
||||
* @return [String] 无法获取时返回 ""
|
||||
*/
|
||||
fun Context.appCpuAbiOf(packageName: String = getPackageName()) = runCatching {
|
||||
ApplicationInfoClass.field { name = "primaryCpuAbi" }.get(getPackageInfoCompat(packageName)?.applicationInfo).string()
|
||||
}.getOrNull() ?: ""
|
||||
|
||||
/**
|
||||
* 得到 APP 图标
|
||||
* @param packageName APP 包名 - 默认为当前 APP
|
||||
* @return [Drawable] 无发获取时返回 [R.drawable.ic_android]
|
||||
*/
|
||||
fun Context.appIconOf(packageName: String = getPackageName()) =
|
||||
getPackageInfoCompat(packageName)?.applicationInfo?.loadIcon(packageManager) ?: resources.drawableOf(R.drawable.ic_android)
|
||||
|
||||
/**
|
||||
* 获取 [Serializable] (兼容)
|
||||
* @param key 键值名称
|
||||
* @return [T] or null
|
||||
*/
|
||||
inline fun <reified T : Serializable> Intent.getSerializableExtraCompat(key: String): T? {
|
||||
@Suppress("DEPRECATION")
|
||||
return if (Build.VERSION.SDK_INT >= 33)
|
||||
getSerializableExtra(key, T::class.java)
|
||||
else getSerializableExtra(key) as? T?
|
||||
}
|
||||
|
||||
/**
|
||||
* [List]<[T]> 转换为 [ArrayList]<[T]>
|
||||
* @return [ArrayList]<[T]>
|
||||
*/
|
||||
fun <T> List<T>.toArrayList() = toMutableList() as ArrayList<T>
|
||||
|
||||
/**
|
||||
* 计算与当前时间戳相差的友好时间
|
||||
* @param now 刚刚
|
||||
* @param second 秒前
|
||||
* @param minute 分钟前
|
||||
* @param hour 小时前
|
||||
* @param day 天前
|
||||
* @param month 月前
|
||||
* @param year 年前
|
||||
* @return [String] 友好时间
|
||||
*/
|
||||
fun Long.difference(now: String, second: String, minute: String, hour: String, day: String, month: String, year: String) =
|
||||
((System.currentTimeMillis() - this) / 1000).toInt().let { diff ->
|
||||
when (diff) {
|
||||
in 0..10 -> now
|
||||
in 11..20 -> "10 $second"
|
||||
in 21..30 -> "20 $second"
|
||||
in 31..40 -> "30 $second"
|
||||
in 41..50 -> "40 $second"
|
||||
in 51..59 -> "50 $second"
|
||||
in 60..3599 -> "${(diff / 60).coerceAtLeast(1)} $minute"
|
||||
in 3600..86399 -> "${diff / 3600} $hour"
|
||||
in 86400..2591999 -> "${diff / 86400} $day"
|
||||
in 2592000..31103999 -> "${diff / 2592000} $month"
|
||||
else -> "${diff / 31104000} $year"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保留小数
|
||||
* @param count 要保留的位数 - 默认 2 位 - 最多 7 位
|
||||
* @return [String] 得到的字符串数字 - 格式化失败返回原始数字的字符串
|
||||
*/
|
||||
fun Number.decimal(count: Int = 2) = runCatching {
|
||||
DecimalFormat(
|
||||
when (count) {
|
||||
0 -> "0"
|
||||
1 -> "0.0"
|
||||
2 -> "0.00"
|
||||
3 -> "0.000"
|
||||
4 -> "0.0000"
|
||||
5 -> "0.00000"
|
||||
6 -> "0.000000"
|
||||
7 -> "0.0000000"
|
||||
else -> "0.0"
|
||||
}
|
||||
).apply { roundingMode = RoundingMode.HALF_UP }.format(this) ?: toString()
|
||||
}.getOrNull() ?: this
|
||||
|
||||
/**
|
||||
* [Long] 转换为 UTC 时间
|
||||
* @return [String]
|
||||
*/
|
||||
fun Long.toUtcTime() = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.ROOT).format(Date(this)) ?: ""
|
||||
|
||||
/**
|
||||
* 弹出 [Toast]
|
||||
* @param msg 提示内容
|
||||
*/
|
||||
fun Context.toast(msg: String) = Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
|
||||
|
||||
/**
|
||||
* 弹出 [Snackbar]
|
||||
* @param msg 提示内容
|
||||
* @param actionText 按钮文本 - 不写默认取消按钮
|
||||
* @param callback 按钮事件回调
|
||||
*/
|
||||
fun Context.snake(msg: String, actionText: String = "", callback: () -> Unit = {}) =
|
||||
Snackbar.make((this as Activity).findViewById(android.R.id.content), msg, Snackbar.LENGTH_LONG).apply {
|
||||
if (actionText.isBlank()) return@apply
|
||||
setActionTextColor(if (isSystemInDarkMode) Color.BLACK else Color.WHITE)
|
||||
setAction(actionText) { callback() }
|
||||
}.show()
|
||||
|
||||
/**
|
||||
* 推送通知
|
||||
* @param channelId 渠道 Id
|
||||
* @param channelName 渠道名称
|
||||
* @param title 标题
|
||||
* @param content 内容
|
||||
* @param icon 图标
|
||||
* @param color 颜色
|
||||
* @param intent [Intent]
|
||||
*/
|
||||
fun Context.pushNotify(channelId: String, channelName: String, title: String, content: String, icon: IconCompat, color: Int, intent: Intent) {
|
||||
getSystemService<NotificationManager>()?.apply {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
||||
createNotificationChannel(NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH))
|
||||
notify((0..999).random(), NotificationCompat.Builder(this@pushNotify, channelId).apply {
|
||||
this.color = color
|
||||
setAutoCancel(true)
|
||||
setContentTitle(title)
|
||||
setContentText(content)
|
||||
setSmallIcon(icon)
|
||||
setContentIntent(PendingIntent.getActivity(this@pushNotify, (0..999).random(), intent, PendingIntent.FLAG_IMMUTABLE))
|
||||
setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
}.build())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到指定页面
|
||||
*
|
||||
* [T] 为指定的 [Activity]
|
||||
* @param isOutSide 是否从外部启动
|
||||
* @param initiate [Intent] 方法体
|
||||
*/
|
||||
inline fun <reified T : Activity> Context.navigate(isOutSide: Boolean = false, initiate: Intent.() -> Unit = {}) = runCatching {
|
||||
startActivity((if (isOutSide) Intent() else Intent(if (this is Service) applicationContext else this, T::class.java)).apply {
|
||||
flags = if (this@navigate !is Activity) Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
else Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
if (isOutSide) component = ComponentName(BuildConfigWrapper.APPLICATION_ID, T::class.java.name)
|
||||
initiate(this)
|
||||
})
|
||||
}.onFailure { toast(msg = "Start ${T::class.java.name} failed") }
|
||||
|
||||
/**
|
||||
* 复制到剪贴板
|
||||
* @param content 要复制的文本
|
||||
*/
|
||||
fun Context.copyToClipboard(content: String) = runCatching {
|
||||
(getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).apply {
|
||||
setPrimaryClip(ClipData.newPlainText(null, content))
|
||||
(primaryClip?.getItemAt(0)?.text ?: "").also {
|
||||
if (it != content) toast(LocaleString.copyFail) else toast(LocaleString.copied)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转 APP 自身设置界面
|
||||
* @param packageName 包名
|
||||
*/
|
||||
fun Context.openSelfSetting(packageName: String = this.packageName) = runCatching {
|
||||
startActivity(Intent().apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
data = Uri.fromParts("package", packageName, null)
|
||||
})
|
||||
}.onFailure { toast(msg = "Cannot open \"$packageName\"") }
|
||||
|
||||
/**
|
||||
* 启动系统浏览器
|
||||
* @param url 网址
|
||||
* @param packageName 指定包名 - 可不填
|
||||
*/
|
||||
fun Context.openBrowser(url: String, packageName: String = "") = runCatching {
|
||||
startActivity(Intent().apply {
|
||||
if (packageName.isNotBlank()) setPackage(packageName)
|
||||
action = Intent.ACTION_VIEW
|
||||
data = Uri.parse(url)
|
||||
/** 防止顶栈一样重叠在自己的 APP 中 */
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
})
|
||||
}.onFailure {
|
||||
if (packageName.isNotBlank()) snake(msg = "Cannot start \"$packageName\"")
|
||||
else snake(msg = "Start system browser failed")
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前 APP 是否可被启动
|
||||
* @param packageName 包名
|
||||
*/
|
||||
fun Context.isAppCanOpened(packageName: String = this.packageName) =
|
||||
runCatching { packageManager?.getLaunchIntentForPackage(packageName) != null }.getOrNull() ?: false
|
||||
|
||||
/**
|
||||
* 启动指定 APP
|
||||
* @param packageName APP 包名 - 默认为当前 APP
|
||||
* @param userId APP 用户 ID - 默认 0
|
||||
*/
|
||||
fun Context.openApp(packageName: String = getPackageName(), userId: Int = 0) = runCatching {
|
||||
ContextClass.method {
|
||||
name = "startActivityAsUser"
|
||||
param(IntentClass, UserHandleClass)
|
||||
}.get(this).call(packageManager.getLaunchIntentForPackage(packageName), UserHandleClass.method { name = "of" }.get().call(userId))
|
||||
}.onFailure { toast(msg = "Cannot start \"$packageName\"${if (userId > 0) " for user $userId" else ""}") }
|
||||
|
||||
/**
|
||||
* 是否有 Root 权限
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val isRootAccess get() = runCatching {
|
||||
@Suppress("DEPRECATION")
|
||||
Shell.rootAccess()
|
||||
}.getOrNull() ?: false
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
* @param cmd 命令
|
||||
* @param isSu 是否使用 Root 权限执行 - 默认:是
|
||||
* @return [String] 执行结果
|
||||
*/
|
||||
fun execShell(cmd: String, isSu: Boolean = true) = runCatching {
|
||||
@Suppress("DEPRECATION")
|
||||
(if (isSu) Shell.su(cmd) else Shell.sh(cmd)).exec().out.let {
|
||||
if (it.isNotEmpty()) it[0].trim() else ""
|
||||
}
|
||||
}.getOrNull() ?: ""
|
||||
|
||||
/**
|
||||
* 隐藏或显示启动器图标
|
||||
*
|
||||
* - 你可能需要 LSPosed 的最新版本以开启高版本系统中隐藏 APP 桌面图标功能
|
||||
* @param isShow 是否显示
|
||||
*/
|
||||
fun Context.hideOrShowLauncherIcon(isShow: Boolean) {
|
||||
packageManager?.setComponentEnabledSetting(
|
||||
ComponentName(packageName, "${BuildConfigWrapper.APPLICATION_ID}.Home"),
|
||||
if (isShow) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取启动器图标状态
|
||||
* @return [Boolean] 是否显示
|
||||
*/
|
||||
val Context.isLauncherIconShowing
|
||||
get() = packageManager?.getComponentEnabledSetting(
|
||||
ComponentName(packageName, "${BuildConfigWrapper.APPLICATION_ID}.Home")
|
||||
) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/10/3.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonIOException
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.reflect.TypeToken
|
||||
|
||||
/**
|
||||
* 创建 [Gson] 实例
|
||||
* @return [Gson]
|
||||
*/
|
||||
val GSON by lazy { GsonBuilder().setLenient().create() ?: error("Gson create failed") }
|
||||
|
||||
/**
|
||||
* 实体类转 Json 字符串
|
||||
* @return [String]
|
||||
* @throws [JsonIOException] 如果不是有效的 Json 数据
|
||||
*/
|
||||
fun Any?.toJson() = GSON.toJson(this) ?: ""
|
||||
|
||||
/**
|
||||
* 实体类转 Json 字符串
|
||||
* @return [String] or null
|
||||
*/
|
||||
fun Any?.toJsonOrNull() = runCatching { toJson() }.getOrNull()
|
||||
|
||||
/**
|
||||
* Json 字符串转实体类
|
||||
* @return [T] or null
|
||||
* @throws [JsonSyntaxException] 如果 Json 格式不正确
|
||||
*/
|
||||
inline fun <reified T> String.toEntity(): T? = takeIf { it.isNotBlank() }.let { GSON.fromJson(this, object : TypeToken<T>() {}.type) }
|
||||
|
||||
/**
|
||||
* Json 字符串转实体类
|
||||
* @return [T] or null
|
||||
*/
|
||||
inline fun <reified T> String.toEntityOrNull(): T? = runCatching { toEntity<T?>() }.getOrNull()
|
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/10/3.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
/**
|
||||
* 创建当前线程池服务
|
||||
* @return [ExecutorService]
|
||||
*/
|
||||
private val currentThreadPool get() = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())
|
||||
|
||||
/**
|
||||
* 创建并启动新的临时线程池
|
||||
*
|
||||
* 等待 [block] 执行完成并自动释放
|
||||
* @param block 方法块
|
||||
*/
|
||||
fun newThread(block: () -> Unit) {
|
||||
currentThreadPool.apply {
|
||||
execute {
|
||||
block()
|
||||
shutdown()
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/10/5.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.tool
|
||||
|
||||
import android.app.Application
|
||||
import android.widget.CompoundButton
|
||||
import com.fankes.apperrorstracking.generated.ModuleAppProperties
|
||||
import com.highcapable.yukihookapi.hook.factory.prefs
|
||||
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
|
||||
import com.microsoft.appcenter.AppCenter
|
||||
import com.microsoft.appcenter.analytics.Analytics
|
||||
import com.microsoft.appcenter.crashes.Crashes
|
||||
|
||||
/**
|
||||
* Microsoft App Center 工具
|
||||
*/
|
||||
object AppAnalyticsTool {
|
||||
|
||||
/** App Secret */
|
||||
private const val APP_CENTER_SECRET = ModuleAppProperties.APP_CENTER_SECRET
|
||||
|
||||
/** 启用匿名统计收集使用情况功能 */
|
||||
private val ENABLE_APP_CENTER_ANALYTICS = PrefsData("_enable_app_center_analytics", true)
|
||||
|
||||
/** 当前实例 */
|
||||
private var instance: Application? = null
|
||||
|
||||
/**
|
||||
* 是否启用匿名统计收集使用情况功能
|
||||
* @return [Boolean]
|
||||
*/
|
||||
private var isEnableAppCenterAnalytics
|
||||
get() = instance?.prefs()?.get(ENABLE_APP_CENTER_ANALYTICS) ?: true
|
||||
set(value) {
|
||||
instance?.prefs()?.edit { put(ENABLE_APP_CENTER_ANALYTICS, value) }
|
||||
}
|
||||
|
||||
/** 是否可用 */
|
||||
val isAvailable = APP_CENTER_SECRET.isNotBlank()
|
||||
|
||||
/** 绑定到 [CompoundButton] 自动设置选中状态 */
|
||||
fun CompoundButton.bindAppAnalytics() {
|
||||
isChecked = isEnableAppCenterAnalytics
|
||||
setOnCheckedChangeListener { button, isChecked ->
|
||||
if (button.isPressed.not()) return@setOnCheckedChangeListener
|
||||
isEnableAppCenterAnalytics = isChecked
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传分析日志
|
||||
* @param name 事件名称
|
||||
* @param data 事件详细内容 - 默认空
|
||||
*/
|
||||
fun trackEvent(name: String, data: HashMap<String, String>? = null) {
|
||||
if (data != null) Analytics.trackEvent(name, data)
|
||||
else Analytics.trackEvent(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 App Center
|
||||
* @param instance 实例
|
||||
*/
|
||||
fun init(instance: Application) {
|
||||
this.instance = instance
|
||||
if (isEnableAppCenterAnalytics && isAvailable)
|
||||
AppCenter.start(instance, APP_CENTER_SECRET, Analytics::class.java, Crashes::class.java)
|
||||
}
|
||||
}
|
@@ -0,0 +1,390 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/12.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.utils.tool
|
||||
|
||||
import android.content.Context
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
|
||||
import com.fankes.apperrorstracking.bean.AppFiltersBean
|
||||
import com.fankes.apperrorstracking.bean.AppInfoBean
|
||||
import com.fankes.apperrorstracking.bean.MutedErrorsAppBean
|
||||
import com.fankes.apperrorstracking.const.PackageName
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.utils.factory.execShell
|
||||
import com.fankes.apperrorstracking.utils.factory.isRootAccess
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.highcapable.yukihookapi.hook.factory.dataChannel
|
||||
import com.highcapable.yukihookapi.hook.param.PackageParam
|
||||
import com.highcapable.yukihookapi.hook.xposed.channel.data.ChannelData
|
||||
|
||||
/**
|
||||
* 系统框架控制工具
|
||||
*/
|
||||
object FrameworkTool {
|
||||
|
||||
private const val CALL_REFRESH_HOST_PREFS_DATA = "call_refresh_host_prefs_data"
|
||||
private const val CALL_APP_ERRORS_DATA_GET = "call_app_errors_data_get"
|
||||
private const val CALL_MUTED_ERRORS_APP_DATA_GET = "call_muted_app_errors_data_get"
|
||||
private const val CALL_APP_ERRORS_DATA_CLEAR = "call_app_errors_data_clear"
|
||||
private const val CALL_UNMUTE_ALL_ERRORS_APPS_DATA = "call_unmute_all_errors_apps_data"
|
||||
private const val CALL_APP_ERRORS_DATA_REMOVE_RESULT = "call_app_errors_data_remove_result"
|
||||
private const val CALL_APP_ERRORS_DATA_CLEAR_RESULT = "call_app_errors_data_clear_result"
|
||||
private const val CALL_MUTED_ERRORS_IF_UNLOCK_RESULT = "call_muted_errors_if_unlock_result"
|
||||
private const val CALL_MUTED_ERRORS_IF_RESTART_RESULT = "call_muted_errors_if_restart_result"
|
||||
private const val CALL_UNMUTE_ERRORS_APP_DATA_RESULT = "call_unmute_errors_app_data_result"
|
||||
private const val CALL_UNMUTE_ALL_ERRORS_APPS_DATA_RESULT = "call_unmute_all_errors_apps_data_result"
|
||||
|
||||
private val CALL_APP_ERROR_DATA_GET = ChannelData<Int>("call_app_error_data_get")
|
||||
private val CALL_OPEN_SPECIFY_APP = ChannelData<Pair<String, Int>>("call_open_specify_app")
|
||||
private val CALL_APP_LIST_DATA_GET = ChannelData<AppFiltersBean>("call_app_info_list_data_get")
|
||||
private val CALL_APP_ERRORS_DATA_REMOVE = ChannelData<AppErrorsInfoBean>("call_app_errors_data_remove")
|
||||
private val CALL_APP_LIST_DATA_GET_RESULT = ChannelData<ArrayList<AppInfoBean>>("call_app_info_list_data_get_result")
|
||||
private val CALL_APP_ERROR_DATA_GET_RESULT = ChannelData<AppErrorsInfoBean>("call_app_error_data_get_result")
|
||||
private val CALL_APP_ERRORS_DATA_GET_RESULT = ChannelData<ArrayList<AppErrorsInfoBean>>("call_app_errors_data_get_result")
|
||||
private val CALL_MUTED_ERRORS_APP_DATA_GET_RESULT = ChannelData<ArrayList<MutedErrorsAppBean>>("call_muted_app_errors_data_get_result")
|
||||
private val CALL_UNMUTE_ERRORS_APP_DATA = ChannelData<MutedErrorsAppBean>("call_unmute_errors_app_data")
|
||||
private val CALL_MUTED_ERRORS_IF_UNLOCK = ChannelData<String>("call_muted_errors_if_unlock")
|
||||
private val CALL_MUTED_ERRORS_IF_RESTART = ChannelData<String>("call_muted_errors_if_restart")
|
||||
|
||||
/**
|
||||
* 宿主注册监听
|
||||
*/
|
||||
object Host {
|
||||
|
||||
/** [PackageParam] 实例 */
|
||||
private var instance: PackageParam? = null
|
||||
|
||||
/**
|
||||
* 注册监听
|
||||
* @param instance 实例
|
||||
* @param initiate 实例方法体
|
||||
* @return [Host]
|
||||
*/
|
||||
fun with(instance: PackageParam, initiate: Host.() -> Unit) = apply { this.instance = instance }.apply(initiate)
|
||||
|
||||
/**
|
||||
* 通知系统框架刷新存储的数据
|
||||
* @param callback 回调
|
||||
*/
|
||||
fun onRefreshFrameworkPrefsData(callback: () -> Unit) = instance?.dataChannel?.wait(CALL_REFRESH_HOST_PREFS_DATA) { callback() }
|
||||
|
||||
/**
|
||||
* 监听使用系统框架打开 APP
|
||||
* @param result 回调包名和用户 ID
|
||||
*/
|
||||
fun onOpenAppUsedFramework(result: (Pair<String, Int>) -> Unit) = instance?.dataChannel?.wait(CALL_OPEN_SPECIFY_APP) { result(it) }
|
||||
|
||||
/**
|
||||
* 监听发送指定 APP 异常信息
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun onPushAppErrorInfoData(result: (Int) -> AppErrorsInfoBean) {
|
||||
instance?.dataChannel?.with { wait(CALL_APP_ERROR_DATA_GET) { put(CALL_APP_ERROR_DATA_GET_RESULT, result(it)) } }
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听发送 APP 异常信息数组
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun onPushAppErrorsInfoData(result: () -> ArrayList<AppErrorsInfoBean>) {
|
||||
instance?.dataChannel?.with { wait(CALL_APP_ERRORS_DATA_GET) { put(CALL_APP_ERRORS_DATA_GET_RESULT, result()) } }
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听移除指定 APP 异常信息
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun onRemoveAppErrorsInfoData(result: (AppErrorsInfoBean) -> Unit) {
|
||||
instance?.dataChannel?.with {
|
||||
wait(CALL_APP_ERRORS_DATA_REMOVE) {
|
||||
result(it)
|
||||
put(CALL_APP_ERRORS_DATA_REMOVE_RESULT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听清空 APP 异常信息数组
|
||||
* @param callback 回调
|
||||
*/
|
||||
fun onClearAppErrorsInfoData(callback: () -> Unit) {
|
||||
instance?.dataChannel?.with {
|
||||
wait(CALL_APP_ERRORS_DATA_CLEAR) {
|
||||
callback()
|
||||
put(CALL_APP_ERRORS_DATA_CLEAR_RESULT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听忽略 APP 的错误直到设备重新解锁
|
||||
* @param result 回调包名
|
||||
*/
|
||||
fun onMutedErrorsIfUnlock(result: (String) -> Unit) {
|
||||
instance?.dataChannel?.with {
|
||||
wait(CALL_MUTED_ERRORS_IF_UNLOCK) {
|
||||
result(it)
|
||||
put(CALL_MUTED_ERRORS_IF_UNLOCK_RESULT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听忽略 APP 的错误直到设备重新启动
|
||||
* @param result 回调包名
|
||||
*/
|
||||
fun onMutedErrorsIfRestart(result: (String) -> Unit) {
|
||||
instance?.dataChannel?.with {
|
||||
wait(CALL_MUTED_ERRORS_IF_RESTART) {
|
||||
result(it)
|
||||
put(CALL_MUTED_ERRORS_IF_RESTART_RESULT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听发送已忽略异常的 APP 信息数组
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun onPushMutedErrorsAppsData(result: () -> ArrayList<MutedErrorsAppBean>) {
|
||||
instance?.dataChannel?.with { wait(CALL_MUTED_ERRORS_APP_DATA_GET) { put(CALL_MUTED_ERRORS_APP_DATA_GET_RESULT, result()) } }
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听取消指定已忽略异常的 APP
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun onUnmuteErrorsApp(result: (MutedErrorsAppBean) -> Unit) {
|
||||
instance?.dataChannel?.with {
|
||||
wait(CALL_UNMUTE_ERRORS_APP_DATA) {
|
||||
result(it)
|
||||
put(CALL_UNMUTE_ERRORS_APP_DATA_RESULT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听取消全部已忽略异常的 APP
|
||||
* @param callback 回调
|
||||
*/
|
||||
fun onUnmuteAllErrorsApps(callback: () -> Unit) {
|
||||
instance?.dataChannel?.with {
|
||||
wait(CALL_UNMUTE_ALL_ERRORS_APPS_DATA) {
|
||||
callback()
|
||||
put(CALL_UNMUTE_ALL_ERRORS_APPS_DATA_RESULT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听发送已安装 APP 列表数组
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun onPushAppListData(result: (AppFiltersBean) -> ArrayList<AppInfoBean>) {
|
||||
instance?.dataChannel?.with { wait(CALL_APP_LIST_DATA_GET) { put(CALL_APP_LIST_DATA_GET_RESULT, result(it)) } }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重启系统
|
||||
* @param context 实例
|
||||
*/
|
||||
fun restartSystem(context: Context) {
|
||||
/** 当 Root 权限获取失败时显示对话框 */
|
||||
fun showWhenAccessRootFail() =
|
||||
context.showDialog {
|
||||
title = LocaleString.accessRootFail
|
||||
msg = LocaleString.accessRootFailTip
|
||||
confirmButton(LocaleString.gotIt)
|
||||
}
|
||||
context.showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.areYouSureRestartSystem
|
||||
confirmButton {
|
||||
if (isRootAccess)
|
||||
execShell(cmd = "reboot")
|
||||
else showWhenAccessRootFail()
|
||||
}
|
||||
neutralButton(LocaleString.fastRestart) {
|
||||
context.showDialog {
|
||||
title = LocaleString.warning
|
||||
msg = LocaleString.fastRestartProblem
|
||||
confirmButton {
|
||||
if (isRootAccess)
|
||||
execShell(cmd = "killall zygote")
|
||||
else showWhenAccessRootFail()
|
||||
}
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否激活
|
||||
* @param context 实例
|
||||
* @param result 成功后回调
|
||||
*/
|
||||
fun checkingActivated(context: Context, result: (Boolean) -> Unit) =
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).checkingVersionEquals(result = result)
|
||||
|
||||
/**
|
||||
* 通知系统框架刷新存储的数据
|
||||
* @param context 实例
|
||||
*/
|
||||
fun refreshFrameworkPrefsData(context: Context) = context.dataChannel(PackageName.SYSTEM_FRAMEWORK).put(CALL_REFRESH_HOST_PREFS_DATA)
|
||||
|
||||
/**
|
||||
* 使用系统框架打开 [packageName]
|
||||
* @param context 实例
|
||||
* @param packageName APP 包名
|
||||
* @param userId APP 用户 ID
|
||||
*/
|
||||
fun openAppUsedFramework(context: Context, packageName: String, userId: Int) =
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).put(CALL_OPEN_SPECIFY_APP, Pair(packageName, userId))
|
||||
|
||||
/**
|
||||
* 获取指定 APP 异常信息
|
||||
* @param context 实例
|
||||
* @param pid 当前进程 ID
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun fetchAppErrorInfoData(context: Context, pid: Int, result: (AppErrorsInfoBean) -> Unit) {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_APP_ERROR_DATA_GET_RESULT) { result(it) }
|
||||
put(CALL_APP_ERROR_DATA_GET, pid)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 APP 异常信息数组
|
||||
* @param context 实例
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun fetchAppErrorsInfoData(context: Context, result: (ArrayList<AppErrorsInfoBean>) -> Unit) {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_APP_ERRORS_DATA_GET_RESULT) { result(it) }
|
||||
put(CALL_APP_ERRORS_DATA_GET)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定 APP 异常信息
|
||||
* @param context 实例
|
||||
* @param appErrorsInfo 指定 APP 异常信息
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun removeAppErrorsInfoData(context: Context, appErrorsInfo: AppErrorsInfoBean, callback: () -> Unit) {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_APP_ERRORS_DATA_REMOVE_RESULT) { callback() }
|
||||
put(CALL_APP_ERRORS_DATA_REMOVE, appErrorsInfo)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空 APP 异常信息数组
|
||||
* @param context 实例
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun clearAppErrorsInfoData(context: Context, callback: () -> Unit) {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_APP_ERRORS_DATA_CLEAR_RESULT) { callback() }
|
||||
put(CALL_APP_ERRORS_DATA_CLEAR)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略 [packageName] 的错误直到设备重新解锁
|
||||
* @param context 实例
|
||||
* @param packageName APP 包名
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun mutedErrorsIfUnlock(context: Context, packageName: String, callback: () -> Unit) {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_MUTED_ERRORS_IF_UNLOCK_RESULT) { callback() }
|
||||
put(CALL_MUTED_ERRORS_IF_UNLOCK, packageName)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略 [packageName] 的错误直到设备重新启动
|
||||
* @param context 实例
|
||||
* @param packageName APP 包名
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun mutedErrorsIfRestart(context: Context, packageName: String, callback: () -> Unit) {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_MUTED_ERRORS_IF_RESTART_RESULT) { callback() }
|
||||
put(CALL_MUTED_ERRORS_IF_RESTART, packageName)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已忽略异常的 APP 信息数组
|
||||
* @param context 实例
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun fetchMutedErrorsAppsData(context: Context, result: (ArrayList<MutedErrorsAppBean>) -> Unit) {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_MUTED_ERRORS_APP_DATA_GET_RESULT) { result(it) }
|
||||
put(CALL_MUTED_ERRORS_APP_DATA_GET)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消指定已忽略异常的 APP
|
||||
* @param context 实例
|
||||
* @param mutedErrorsApp 指定已忽略异常的 APP 信息
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun unmuteErrorsApp(context: Context, mutedErrorsApp: MutedErrorsAppBean, callback: () -> Unit) {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_UNMUTE_ERRORS_APP_DATA_RESULT) { callback() }
|
||||
put(CALL_UNMUTE_ERRORS_APP_DATA, mutedErrorsApp)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消全部已忽略异常的 APP
|
||||
* @param context 实例
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun unmuteAllErrorsApps(context: Context, callback: () -> Unit) {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_UNMUTE_ALL_ERRORS_APPS_DATA_RESULT) { callback() }
|
||||
put(CALL_UNMUTE_ALL_ERRORS_APPS_DATA)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已安装 APP 列表数组
|
||||
* @param context 实例
|
||||
* @param appFilters 过滤条件
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun fetchAppListData(context: Context, appFilters: AppFiltersBean, result: (ArrayList<AppInfoBean>) -> Unit) {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_APP_LIST_DATA_GET_RESULT) { result(it) }
|
||||
put(CALL_APP_LIST_DATA_GET, appFilters)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2023/1/23.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.utils.tool
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.icu.text.SimpleDateFormat
|
||||
import android.icu.util.Calendar
|
||||
import android.icu.util.TimeZone
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.utils.factory.openBrowser
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.json.JSONObject
|
||||
import java.io.IOException
|
||||
import java.io.Serializable
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* 获取 GitHub Release 最新版本工具类
|
||||
*/
|
||||
object GithubReleaseTool {
|
||||
|
||||
/** 仓库作者 */
|
||||
private const val REPO_AUTHOR = "KitsunePie"
|
||||
|
||||
/** 仓库名称 */
|
||||
private const val REPO_NAME = "AppErrorsTracking"
|
||||
|
||||
/**
|
||||
* 获取最新版本信息
|
||||
* @param context 实例
|
||||
* @param version 当前版本
|
||||
* @param result 成功后回调 - ([String] 最新版本,[Function] 更新对话框方法体)
|
||||
*/
|
||||
fun checkingForUpdate(context: Context, version: String, result: (String, () -> Unit) -> Unit) = runCatching {
|
||||
OkHttpClient().newBuilder().build().newCall(
|
||||
Request.Builder()
|
||||
.url("https://api.github.com/repos/$REPO_AUTHOR/$REPO_NAME/releases/latest")
|
||||
.get()
|
||||
.build()
|
||||
).enqueue(object : Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {}
|
||||
|
||||
override fun onResponse(call: Call, response: Response) = runCatching {
|
||||
JSONObject(response.body.string()).apply {
|
||||
GithubReleaseBean(
|
||||
name = getString("name"),
|
||||
htmlUrl = getString("html_url"),
|
||||
content = getString("body"),
|
||||
date = getString("published_at").localTime()
|
||||
).apply {
|
||||
fun showUpdate() = context.showDialog {
|
||||
title = LocaleString.latestVersion(name)
|
||||
msg = LocaleString.latestVersionTip(date, content)
|
||||
confirmButton(LocaleString.updateNow) { context.openBrowser(htmlUrl) }
|
||||
cancelButton()
|
||||
}
|
||||
if (name != version) (context as? Activity?)?.runOnUiThread {
|
||||
showUpdate()
|
||||
result(name) { showUpdate() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}.getOrNull().let {}
|
||||
})
|
||||
}.getOrNull().let {}
|
||||
|
||||
/**
|
||||
* 格式化时间为本地时区
|
||||
* @return [String] 本地时区时间
|
||||
*/
|
||||
private fun String.localTime() = replace("T", " ").replace("Z", "").let {
|
||||
runCatching {
|
||||
val local = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ROOT).apply { timeZone = Calendar.getInstance().timeZone }
|
||||
val current = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ROOT).apply { timeZone = TimeZone.getTimeZone("GMT") }
|
||||
local.format(current.parse(it))
|
||||
}.getOrNull() ?: it
|
||||
}
|
||||
|
||||
/**
|
||||
* GitHub Release bean
|
||||
* @param name 版本名称
|
||||
* @param htmlUrl 网页地址
|
||||
* @param content 更新日志
|
||||
* @param date 发布时间
|
||||
*/
|
||||
private data class GithubReleaseBean(
|
||||
var name: String,
|
||||
var htmlUrl: String,
|
||||
var content: String,
|
||||
var date: String
|
||||
) : Serializable
|
||||
}
|
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/13.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.tool
|
||||
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
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/"
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 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
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2023/9/19.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.fankes.apperrorstracking.wrapper
|
||||
|
||||
import com.fankes.apperrorstracking.BuildConfig
|
||||
|
||||
/**
|
||||
* 对 [BuildConfig] 的包装
|
||||
*/
|
||||
object BuildConfigWrapper {
|
||||
const val APPLICATION_ID = BuildConfig.APPLICATION_ID
|
||||
const val VERSION_NAME = BuildConfig.VERSION_NAME
|
||||
const val VERSION_CODE = BuildConfig.VERSION_CODE
|
||||
val isDebug = BuildConfig.DEBUG
|
||||
}
|
6
module-app/src/main/res/drawable-night/bg_dark_round.xml
Executable file
6
module-app/src/main/res/drawable-night/bg_dark_round.xml
Executable file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#66A6A6A6" />
|
||||
<corners android:radius="15dp" />
|
||||
</shape>
|
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="#777777">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#666E6E6E" />
|
||||
<corners android:radius="15dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#666E6E6E" />
|
||||
<corners android:radius="15dp" />
|
||||
</shape>
|
6
module-app/src/main/res/drawable/bg_black_round.xml
Normal file
6
module-app/src/main/res/drawable/bg_black_round.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#292929" />
|
||||
<corners android:radius="5dp" />
|
||||
</shape>
|
6
module-app/src/main/res/drawable/bg_blue_round.xml
Normal file
6
module-app/src/main/res/drawable/bg_blue_round.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#2196F3" />
|
||||
<corners android:radius="5dp" />
|
||||
</shape>
|
10
module-app/src/main/res/drawable/bg_button_round.xml
Normal file
10
module-app/src/main/res/drawable/bg_button_round.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="#777777">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#66DAD9D9" />
|
||||
<corners android:radius="15dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
6
module-app/src/main/res/drawable/bg_dark_round.xml
Executable file
6
module-app/src/main/res/drawable/bg_dark_round.xml
Executable file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#661B1B1B" />
|
||||
<corners android:radius="15dp" />
|
||||
</shape>
|
6
module-app/src/main/res/drawable/bg_green_round.xml
Executable file
6
module-app/src/main/res/drawable/bg_green_round.xml
Executable file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#26A69A" />
|
||||
<corners android:radius="15dp" />
|
||||
</shape>
|
8
module-app/src/main/res/drawable/bg_logger_d_round.xml
Normal file
8
module-app/src/main/res/drawable/bg_logger_d_round.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#565656" />
|
||||
<corners
|
||||
android:bottomLeftRadius="10dp"
|
||||
android:topLeftRadius="10dp" />
|
||||
</shape>
|
8
module-app/src/main/res/drawable/bg_logger_e_round.xml
Normal file
8
module-app/src/main/res/drawable/bg_logger_e_round.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#E53935" />
|
||||
<corners
|
||||
android:bottomLeftRadius="10dp"
|
||||
android:topLeftRadius="10dp" />
|
||||
</shape>
|
8
module-app/src/main/res/drawable/bg_logger_i_round.xml
Normal file
8
module-app/src/main/res/drawable/bg_logger_i_round.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#3974CB" />
|
||||
<corners
|
||||
android:bottomLeftRadius="10dp"
|
||||
android:topLeftRadius="10dp" />
|
||||
</shape>
|
8
module-app/src/main/res/drawable/bg_logger_w_round.xml
Normal file
8
module-app/src/main/res/drawable/bg_logger_w_round.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#FB8C00" />
|
||||
<corners
|
||||
android:bottomLeftRadius="10dp"
|
||||
android:topLeftRadius="10dp" />
|
||||
</shape>
|
6
module-app/src/main/res/drawable/bg_orange_round.xml
Executable file
6
module-app/src/main/res/drawable/bg_orange_round.xml
Executable file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#FF7043" />
|
||||
<corners android:radius="15dp" />
|
||||
</shape>
|
10
module-app/src/main/res/drawable/bg_permotion_ripple.xml
Normal file
10
module-app/src/main/res/drawable/bg_permotion_ripple.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="#777777">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#66E4E4E4" />
|
||||
<corners android:radius="15dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
6
module-app/src/main/res/drawable/bg_permotion_round.xml
Normal file
6
module-app/src/main/res/drawable/bg_permotion_round.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#66E4E4E4" />
|
||||
<corners android:radius="15dp" />
|
||||
</shape>
|
6
module-app/src/main/res/drawable/bg_red_round.xml
Normal file
6
module-app/src/main/res/drawable/bg_red_round.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#EF5350" />
|
||||
<corners android:radius="5dp" />
|
||||
</shape>
|
6
module-app/src/main/res/drawable/bg_stack_round.xml
Normal file
6
module-app/src/main/res/drawable/bg_stack_round.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#FF14171F" />
|
||||
<corners android:radius="15dp" />
|
||||
</shape>
|
6
module-app/src/main/res/drawable/bg_yellow_round.xml
Executable file
6
module-app/src/main/res/drawable/bg_yellow_round.xml
Executable file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#FF9800" />
|
||||
<corners android:radius="15dp" />
|
||||
</shape>
|
15
module-app/src/main/res/drawable/ic_android.xml
Normal file
15
module-app/src/main/res/drawable/ic_android.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="250dp"
|
||||
android:height="250dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024"
|
||||
tools:ignore="VectorRaster">
|
||||
<path
|
||||
android:fillColor="#A0C538"
|
||||
android:pathData="M142.2,56.9h739.6a85.3,85.3 0,0 1,85.3 85.3v739.6a85.3,85.3 0,0 1,-85.3 85.3H142.2a85.3,85.3 0,0 1,-85.3 -85.3V142.2a85.3,85.3 0,0 1,85.3 -85.3z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M636.5,561.5c0,14.5 11.6,26.7 25.7,26.7 14.4,0 25.7,-12.1 25.7,-26.7v-102.7c0,-14.7 -11.4,-26.4 -25.7,-26.4 -14.2,0 -25.7,11.7 -25.7,26.4v102.7zM544.6,371.8c0,-6.9 5.8,-12.6 12.6,-12.6 7.1,0 12.9,5.6 12.9,12.6a12.7,12.7 0,1 1,-25.5 0zM446.9,371.8a12.5,12.5 0,0 1,25.1 0,12.6 12.6,0 0,1 -12.6,12.8 12.6,12.6 0,0 1,-12.4 -12.8zM414.1,641.7h22.7v57.2c0,14.3 11.1,26.4 25.7,26.4 13.9,0 25.7,-12.1 25.7,-26.4v-57.2h39.9v57.2c0,14.3 10.9,26.4 25.5,26.4 14.4,0 25.7,-12.1 25.7,-26.4v-57.2h22.7c11.3,0 20.1,-9.1 20.1,-20.4L622.2,433.1L394,433.1v188.3c0,11.2 8.8,20.4 20.2,20.4zM621.7,416.6c-3.4,-35.5 -26.6,-65.2 -60,-80.8l21,-31c1.4,-2 1.1,-4.6 -0.6,-5.4 -1.7,-1.1 -3.6,-0.4 -5.1,1.3l-22.1,32.1c-14.8,-5.4 -30.4,-8.9 -47.3,-8.9s-33,3.5 -47.6,8.9l-21.6,-32.1c-1.5,-1.7 -3.4,-2.8 -5.1,-1.3 -1.7,1.1 -2.2,3.4 -0.9,5.4l21.2,31c-33.4,16 -57,45.1 -60,80.8h228.2zM327.1,561.5c0,14.5 11.2,26.7 25.7,26.7 13.9,0 25.7,-12.1 25.7,-26.7v-102.7c0,-14.7 -11.8,-26.4 -25.7,-26.4 -14.6,0 -25.7,11.7 -25.7,26.4v102.7z"
|
||||
tools:ignore="VectorPath" />
|
||||
</vector>
|
12
module-app/src/main/res/drawable/ic_appcenter.xml
Normal file
12
module-app/src/main/res/drawable/ic_appcenter.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="64"
|
||||
android:viewportHeight="64">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M44.5,49.92L57,51.79L44.5,57L7,44.5L44.5,49.92L44.5,7L57,12.21L57,51.79L44.5,49.92Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M19.5,15.33L24.29,16.38L24.29,23.88L32,18.46L38.25,20.54L38.25,37.21L32,39.29L24.08,33.88L24.08,41.38L19.5,42.42L11.17,35.13L11.17,32L15.96,28.88L11.17,25.75L11.17,22.63L19.5,15.33ZM24.08,28.88L32,33.04L32,24.71L24.08,28.88ZM13.67,23.88L19.5,26.79L19.5,19.5L13.67,23.88ZM19.5,38.25L19.5,30.96L13.67,33.88L19.5,38.25Z" />
|
||||
</vector>
|
9
module-app/src/main/res/drawable/ic_back.xml
Normal file
9
module-app/src/main/res/drawable/ic_back.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="192dp"
|
||||
android:height="150.39165dp"
|
||||
android:viewportWidth="1307"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M268.7,566.5h929.6c36.3,0 72.6,-29 72.6,-72.6 0,-36.3 -29,-72.6 -72.6,-72.6H305l297.8,-297.8c29,-29 29,-72.6 0,-101.7 -29,-29 -72.6,-29 -101.7,0L72.6,450.3c-14.5,14.5 -21.8,36.3 -21.8,58.1 0,21.8 0,43.6 21.8,58.1l428.5,428.5c29,29 72.6,29 101.7,0 29,-29 29,-72.6 0,-101.7l-334.1,-326.8z" />
|
||||
</vector>
|
10
module-app/src/main/res/drawable/ic_baseline_bug_report.xml
Normal file
10
module-app/src/main/res/drawable/ic_baseline_bug_report.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#ffffff"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z" />
|
||||
</vector>
|
10
module-app/src/main/res/drawable/ic_baseline_close.xml
Normal file
10
module-app/src/main/res/drawable/ic_baseline_close.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#ffffff"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
|
||||
</vector>
|
10
module-app/src/main/res/drawable/ic_baseline_eject.xml
Normal file
10
module-app/src/main/res/drawable/ic_baseline_eject.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M5,17h14v2L5,19zM12,5L5.33,15h13.34z" />
|
||||
</vector>
|
10
module-app/src/main/res/drawable/ic_baseline_info.xml
Normal file
10
module-app/src/main/res/drawable/ic_baseline_info.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#ffffff"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z" />
|
||||
</vector>
|
10
module-app/src/main/res/drawable/ic_baseline_refresh.xml
Normal file
10
module-app/src/main/res/drawable/ic_baseline_refresh.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#ffffff"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z" />
|
||||
</vector>
|
9
module-app/src/main/res/drawable/ic_batch.xml
Normal file
9
module-app/src/main/res/drawable/ic_batch.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M851.5,239.1l-66.6,24.6v499.7l66.6,24.6c29.7,10.8 55.3,-17.9 55.3,-65V304.1c0,-47.1 -25.6,-76.3 -55.3,-65M362.5,374.8l-185.3,23.6c-7.2,1 -13.3,-10.2 -13.3,-24.6s5.6,-27.1 13.3,-28.7l185.3,-35.8c11.3,-2 20.5,10.8 20.5,29.2 -0.5,18.9 -9.7,34.8 -20.5,36.4M164.4,503.8c0,-14.3 5.6,-26.6 13.3,-26.6l93.7,-3.1c9.2,-0.5 16.9,12.8 16.9,29.7s-7.7,30.2 -16.9,29.7l-93.7,-3.1c-7.7,0 -13.3,-11.8 -13.3,-26.6m135.2,182.8l-122.4,-23.6c-7.2,-1.5 -13.3,-14.3 -13.3,-28.7s5.6,-25.6 13.3,-24.6l122.4,15.9c9.7,1 17.9,16.4 17.9,33.3s-8.2,29.2 -17.9,27.6M414.2,110.1L145.4,209.9c-22,8.2 -38.9,50.7 -38.9,95.7v397.3c0,45.1 16.9,87.6 38.9,95.7l268.8,99.8c42,15.9 78.8,-25.6 78.8,-93.2V203.3c0,-67.6 -36.9,-109.1 -78.8,-93.2m250.4,129L558.1,278.5v470.5l106.5,39.4c29.7,10.8 55.3,-17.9 55.3,-65V304.1c0,-47.1 -26.1,-76.3 -55.3,-65" />
|
||||
</vector>
|
9
module-app/src/main/res/drawable/ic_clear.xml
Normal file
9
module-app/src/main/res/drawable/ic_clear.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M785.8,927.4h-64.9c-9.7,0 -16.9,-9.7 -14.3,-19.5 0,-0.6 0,-0.6 0.6,-1.3 4.5,-16.9 -18.1,-26.6 -27.2,-11.7 -7.2,11 -13,20.2 -16.9,26a14.8,14.8 0,0 1,-12.3 6.5h-56.5c-9.7,0 -16.9,-9.7 -14.3,-19.5 0,-0.6 0,-0.6 0.6,-1.3 4.5,-16.9 -18.2,-26.6 -27.3,-11.7 -7.1,11 -13,20.2 -16.9,26a14.8,14.8 0,0 1,-12.3 6.5h-55.8c-9.7,0 -16.9,-9.7 -14.3,-19.5 0,-0.6 0,-0.6 0.6,-1.3 4.5,-16.9 -18.2,-26.6 -27.3,-11.7 -7.1,11 -13,20.2 -16.9,26a14.8,14.8 0,0 1,-12.3 6.5H360c-9.7,0 -16.9,-9.7 -14.3,-19.5 0,-0.6 0,-0.6 0.6,-1.3 4.5,-16.9 -18.2,-26.6 -27.3,-11.7 -7.1,11 -13,20.2 -16.9,26a14.8,14.8 0,0 1,-12.4 6.5H207.5a13.1,13.1 0,0 1,-7.1 -1.9c-43.5,-25.3 -62.9,-84.4 0.6,-138.9 42.2,-36.4 46.7,-94.8 45.4,-147.3 0,-8.4 6.5,-15.6 14.9,-15.6h526.4c7.1,0 13,4.5 14.3,11.7 14.9,64.9 20.1,116.2 32.4,205.8 8.4,65.5 -48.7,86.3 -48.7,86.3zM215.3,570.4c-20.1,0 -29.2,-14.9 -20.8,-32.5l70.8,-150.6c8.4,-18.2 31.8,-32.5 51.3,-32.5h113.6a14.7,14.7 0,0 0,14.9 -14.9V147.8c0.6,-28.6 23.4,-51.3 51.3,-51.3h42.2c27.9,0 51.3,22.7 51.3,51.3v190.8c0,8.4 6.5,14.9 14.9,14.9h109.7c20.1,0 42.8,14.9 51.3,32.4l70.7,150.6c8.5,18.2 -0.6,32.4 -20.7,32.4l-600.4,1.3z" />
|
||||
</vector>
|
12
module-app/src/main/res/drawable/ic_copy.xml
Normal file
12
module-app/src/main/res/drawable/ic_copy.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M316.8,784h377.6v140.8c0,42.2 -34.6,76.8 -76.8,76.8h-518.4c-42.2,0 -76.8,-34.6 -76.8,-76.8v-518.4c0,-42.2 34.6,-76.8 76.8,-76.8h140.8v377.6c0,42.2 34.6,76.8 76.8,76.8z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M1008,105.6v518.4c0,42.2 -34.6,76.8 -76.8,76.8h-518.4c-42.2,0 -76.8,-34.6 -76.8,-76.8v-518.4c0,-42.2 34.6,-76.8 76.8,-76.8h518.4c42.2,0 76.8,34.6 76.8,76.8z" />
|
||||
</vector>
|
14
module-app/src/main/res/drawable/ic_cpp.xml
Normal file
14
module-app/src/main/res/drawable/ic_cpp.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="250dp"
|
||||
android:height="250dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024"
|
||||
tools:ignore="VectorRaster">
|
||||
<path
|
||||
android:fillColor="#1D88E5"
|
||||
android:pathData="M403.5,880.6c-202.8,0 -368.6,-165.9 -368.6,-368.6s165.9,-368.6 368.6,-368.6c116.7,0 223.2,54.3 292.9,145.4L563.2,367.6c-41,-44 -98.3,-70.7 -158.7,-70.7 -117.8,0 -215,96.3 -215,215s96.3,215 215,215c60.4,0 117.8,-26.6 158.7,-70.7l134.1,78.8C628.7,826.4 520.2,880.6 403.5,880.6z" />
|
||||
<path
|
||||
android:fillColor="#1D88E5"
|
||||
android:pathData="M772.1,542.7L711.7,542.7v61.4h-62.5v-61.4L588.8,542.7v-61.4h60.4v-61.4h62.5v61.4h60.4v61.4zM989.2,542.7L926.7,542.7v61.4h-60.4v-61.4L803.8,542.7v-61.4h62.5v-61.4h60.4v61.4h62.5v61.4z" />
|
||||
</vector>
|
11
module-app/src/main/res/drawable/ic_debug.xml
Normal file
11
module-app/src/main/res/drawable/ic_debug.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="250dp"
|
||||
android:height="250dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024"
|
||||
tools:ignore="VectorRaster">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M1024,577.8c-1,34.9 -30.4,62.2 -65.3,62.2H848v32c0,43.7 -9.8,85.2 -27.2,122.3l120.5,120.5c25,25 25,65.5 0,90.5 -25,25 -65.5,25 -90.5,0l-109.5,-109.5C691.8,935.9 628.7,960 560,960V472c0,-13.3 -10.7,-24 -24,-24h-48c-13.3,0 -24,10.7 -24,24v488c-68.7,0 -131.8,-24.1 -181.3,-64.2l-109.5,109.5c-25,25 -65.5,25 -90.5,0 -25,-25 -25,-65.5 0,-90.5l120.5,-120.5C185.8,757.2 176,715.7 176,672v-32H65.3C30.5,640 1,612.7 0,577.8 -1,541.6 28.1,512 64,512h112v-117.5l-93.3,-93.3c-25,-25 -25,-65.5 0,-90.5 25,-25 65.5,-25 90.5,0L282.5,320h459l109.3,-109.3c25,-25 65.5,-25 90.5,0 25,25 25,65.5 0,90.5L848,394.5V512h112c35.9,0 65,29.6 64,65.8zM514,0c-123.7,0 -224,100.3 -224,224h448C738,100.3 637.7,0 514,0z" />
|
||||
</vector>
|
9
module-app/src/main/res/drawable/ic_exception.xml
Normal file
9
module-app/src/main/res/drawable/ic_exception.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="250dp"
|
||||
android:height="250dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M511,0a376.4,376.4 0,0 0,-265.1 106.8,365.1 365.1,0 0,0 -108,258.6v498.7h742L880,365.5a362.4,362.4 0,0 0,-108.9 -258.6A374.9,374.9 0,0 0,511 0zM480.3,744.6L469.5,502.9L286.9,502.9l239.9,-303.1 20.5,195.2 174.1,6.1 -243.2,343.3zM0.1,966.4a55.6,55.6 0,0 1,55.6 -56.2h910.6a57.6,57.6 0,0 1,39.3 16.3,58.7 58.7,0 0,1 16.4,39.9 55.7,55.7 0,0 1,-55.7 55.6L55.6,1022.1a55.6,55.6 0,0 1,-55.5 -55.6z" />
|
||||
</vector>
|
12
module-app/src/main/res/drawable/ic_export.xml
Normal file
12
module-app/src/main/res/drawable/ic_export.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M768,678.4v108.8H256v-108.8c0,-12.8 -6.4,-19.2 -19.2,-19.2H147.2c-12.8,0 -19.2,6.4 -19.2,19.2v172.8c0,38.4 25.6,64 64,64h640c38.4,0 64,-25.6 64,-64v-172.8c0,-12.8 -6.4,-19.2 -19.2,-19.2h-89.6c-12.8,0 -19.2,6.4 -19.2,19.2z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M499.2,115.2L281.6,390.4c-12.8,12.8 0,32 12.8,32H448v268.8c0,6.4 6.4,12.8 19.2,12.8h89.6c12.8,0 19.2,-6.4 19.2,-19.2V416h153.6c19.2,0 25.6,-19.2 12.8,-32L524.8,115.2c-6.4,-6.4 -19.2,-6.4 -25.6,0z" />
|
||||
</vector>
|
9
module-app/src/main/res/drawable/ic_filter.xml
Normal file
9
module-app/src/main/res/drawable/ic_filter.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="184.6dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1260"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M419.2,548.3L17,108.9C-23.6,64.4 13,0 79.7,0h846.5c66.6,0 103.3,64.4 62.8,108.9l-402.2,439.3v472.7c0,1.6 -1.3,2.9 -2.8,2.9a8.3,8.3 0,0 1,-5.2 -1.8l-154.3,-120a13.7,13.7 0,0 1,-5.3 -10.8L419.2,548.3zM838.2,511.9L1257.3,511.9L1257.3,597.2h-419.1L838.2,511.9zM838.2,682.5L1257.3,682.5v85.3h-419.1L838.2,682.5zM838.2,853.2L1257.3,853.2v85.3h-419.1v-85.3z" />
|
||||
</vector>
|
52
module-app/src/main/res/drawable/ic_function.xml
Normal file
52
module-app/src/main/res/drawable/ic_function.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M11,16V42"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M24,29V42"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M24,19V6"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M37,6V32"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M11,16C13.761,16 16,13.761 16,11C16,8.239 13.761,6 11,6C8.239,6 6,8.239 6,11C6,13.761 8.239,16 11,16Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M24,29C26.761,29 29,26.761 29,24C29,21.239 26.761,19 24,19C21.239,19 19,21.239 19,24C19,26.761 21.239,29 24,29Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M37,42C39.761,42 42,39.761 42,37C42,34.239 39.761,32 37,32C34.239,32 32,34.239 32,37C32,39.761 34.239,42 37,42Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
9
module-app/src/main/res/drawable/ic_github.xml
Normal file
9
module-app/src/main/res/drawable/ic_github.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M512,12.6c-282.8,0 -512,229.2 -512,512 0,226.2 146.7,418.1 350.1,485.8 25.6,4.7 35,-11.1 35,-24.6 0,-12.2 -0.5,-52.5 -0.7,-95.3 -142.5,31 -172.5,-60.4 -172.5,-60.4 -23.3,-59.2 -56.8,-74.9 -56.8,-74.9 -46.5,-31.8 3.5,-31.1 3.5,-31.1 51.4,3.6 78.5,52.8 78.5,52.8 45.7,78.3 119.8,55.6 149,42.6 4.6,-33.1 17.9,-55.7 32.5,-68.5 -113.7,-12.9 -233.3,-56.9 -233.3,-253 0,-55.9 20,-101.6 52.8,-137.4 -5.3,-12.9 -22.8,-65 5,-135.5 0,0 43,-13.8 140.8,52.5 40.8,-11.4 84.6,-17 128.2,-17.2 43.5,0.2 87.3,5.9 128.3,17.2 97.7,-66.2 140.6,-52.5 140.6,-52.5 27.9,70.5 10.3,122.6 5,135.5 32.8,35.8 52.7,81.5 52.7,137.4 0,196.6 -119.8,239.9 -233.8,252.6 18.4,15.9 34.7,47 34.7,94.8 0,68.5 -0.6,123.6 -0.6,140.5 0,13.6 9.2,29.6 35.2,24.6 203.3,-67.8 349.9,-259.6 349.9,-485.8 0,-282.8 -229.2,-512 -512,-512z" />
|
||||
</vector>
|
30
module-app/src/main/res/drawable/ic_global.xml
Normal file
30
module-app/src/main/res/drawable/ic_global.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M18,6H8C6.895,6 6,6.895 6,8V18C6,19.105 6.895,20 8,20H18C19.105,20 20,19.105 20,18V8C20,6.895 19.105,6 18,6Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M18,28H8C6.895,28 6,28.895 6,30V40C6,41.105 6.895,42 8,42H18C19.105,42 20,41.105 20,40V30C20,28.895 19.105,28 18,28Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M40,6H30C28.895,6 28,6.895 28,8V18C28,19.105 28.895,20 30,20H40C41.105,20 42,19.105 42,18V8C42,6.895 41.105,6 40,6Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M40,28H30C28.895,28 28,28.895 28,30V40C28,41.105 28.895,42 30,42H40C41.105,42 42,41.105 42,40V30C42,28.895 41.105,28 40,28Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
25
module-app/src/main/res/drawable/ic_home.xml
Normal file
25
module-app/src/main/res/drawable/ic_home.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,18V42H39V18L24,6L9,18Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29V42H29V29H19Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,42H39"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round" />
|
||||
</vector>
|
26
module-app/src/main/res/drawable/ic_java.xml
Normal file
26
module-app/src/main/res/drawable/ic_java.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="250dp"
|
||||
android:height="250dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024"
|
||||
tools:ignore="VectorRaster">
|
||||
<path
|
||||
android:fillColor="#FF1515"
|
||||
android:pathData="M747,175.6c-29.7,21 -58.4,38.9 -90.1,64.5 -24.1,19.5 -67.6,46.6 -69.6,82.4 -3.6,54.8 80.9,106 36.4,175.6 -16.9,26.6 -46.1,37.9 -82.4,54.3 -4.6,-8.2 9.7,-14.8 15.4,-23 56.3,-81.4 -58.4,-108.5 -44,-208.9 13.8,-97.8 128,-131.6 234.5,-144.9z" />
|
||||
<path
|
||||
android:fillColor="#2365C4"
|
||||
android:pathData="M388.1,511c-26.6,12.3 -71.2,14.8 -90.1,41.5 19.5,14.8 48.1,14.3 74.8,15.4 109.1,5.1 245.2,-4.6 337.9,-20.5 3.1,6.7 -12.8,18.4 -23,25.6 -58.4,43 -240.6,54.8 -366.6,46.6 -42,-3.1 -138.2,-13.8 -139.3,-51.7 -1.5,-45.6 116.7,-50.7 164.9,-54.3 9.7,-1 27.6,-4.6 41.5,-2.6zM333.8,626.7c12.3,1.5 -8.7,8.7 -5.1,17.9 44.5,44 182.3,31.2 250.4,17.9 14.3,-3.1 28.2,-11.3 38.9,-10.2 25.6,2.6 42.5,32.3 64.5,36.4 -78.3,35.3 -229.9,52.2 -340.5,30.7 -28.7,-5.6 -78.3,-21 -79.9,-44 -2.6,-30.7 49.2,-43.5 71.7,-48.6zM367.6,732.7c8.2,2.6 -3.1,7.2 -2.6,10.2 23.6,41 139.8,26.1 198.7,12.8 11.8,-3.1 23.6,-11.3 33.8,-10.2 29.7,2 41.5,33.3 67.1,38.9 -82.4,50.2 -281.6,70.7 -364,7.7 -4.1,-46.1 32.8,-51.2 67.1,-59.4z" />
|
||||
<path
|
||||
android:fillColor="#2365C4"
|
||||
android:pathData="M292.9,810c-24.6,6.1 -88.1,-2.6 -90.1,30.7 -1,12.8 21.5,28.2 36.4,33.8 84,31.7 252.9,36.9 392.2,20.5 64.5,-7.7 185.9,-29.2 170.5,-95.2 19.5,2.6 36.9,14.8 38.9,33.8 7.7,71.2 -155.6,101.4 -221.7,108.5 -143.9,15.9 -323.1,12.8 -433.7,-25.6 -35.8,-12.3 -79.4,-35.3 -77.3,-69.6 2,-58.4 141.3,-74.2 184.8,-36.9z" />
|
||||
<path
|
||||
android:fillColor="#2365C4"
|
||||
android:pathData="M512,1024c-96.8,-10.8 -190,-24.6 -268.3,-59.4 205.3,49.2 504.3,45.6 647.7,-59.4 7.7,-5.6 14.8,-16.9 25.6,-15.4 -36.4,108.5 -175.1,115.7 -293.9,134.1H512z" />
|
||||
<path
|
||||
android:fillColor="#FF1515"
|
||||
android:pathData="M579.1,0c17.9,16.9 30.7,48.6 30.7,82.4 0,99.8 -106,157.7 -157.2,224.3 -11.3,14.8 -26.1,37.9 -25.6,62 0.5,54.3 56.8,115.2 77.3,159.7 -36.4,-23.6 -79.9,-55.3 -111.1,-92.7 -30.7,-37.4 -62,-97.3 -33.8,-149.5 42.5,-78.3 169.5,-124.9 214,-208.9 11.3,-20.5 20,-51.7 5.6,-77.3z" />
|
||||
<path
|
||||
android:fillColor="#2365C4"
|
||||
android:pathData="M731.1,536.6c53.2,-45.6 143.4,-27.6 146.9,49.2 4.6,89.6 -93.7,140.3 -165.4,144.4 32.8,-31.2 120.3,-81.9 103.4,-154.6 -6.7,-29.7 -43,-47.6 -85,-38.9z" />
|
||||
</vector>
|
17
module-app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
17
module-app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108"
|
||||
tools:ignore="VectorRaster">
|
||||
<group
|
||||
android:scaleX="0.038671874"
|
||||
android:scaleY="0.038671874"
|
||||
android:translateX="34.2"
|
||||
android:translateY="34.2">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M1024,577.8c-1,34.9 -30.4,62.2 -65.3,62.2H848v32c0,43.7 -9.8,85.2 -27.2,122.3l120.5,120.5c25,25 25,65.5 0,90.5 -25,25 -65.5,25 -90.5,0l-109.5,-109.5C691.8,935.9 628.7,960 560,960V472c0,-13.3 -10.7,-24 -24,-24h-48c-13.3,0 -24,10.7 -24,24v488c-68.7,0 -131.8,-24.1 -181.3,-64.2l-109.5,109.5c-25,25 -65.5,25 -90.5,0 -25,-25 -25,-65.5 0,-90.5l120.5,-120.5C185.8,757.2 176,715.7 176,672v-32H65.3C30.5,640 1,612.7 0,577.8 -1,541.6 28.1,512 64,512h112v-117.5l-93.3,-93.3c-25,-25 -25,-65.5 0,-90.5 25,-25 65.5,-25 90.5,0L282.5,320h459l109.3,-109.3c25,-25 65.5,-25 90.5,0 25,25 25,65.5 0,90.5L848,394.5V512h112c35.9,0 65,29.6 64,65.8zM514,0c-123.7,0 -224,100.3 -224,224h448C738,100.3 637.7,0 514,0z" />
|
||||
</group>
|
||||
</vector>
|
10
module-app/src/main/res/drawable/ic_notify.xml
Normal file
10
module-app/src/main/res/drawable/ic_notify.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="m967.6,570.6c-0.9,31.1 -27.1,55.3 -58.1,55.3h-98.5v28.5c0,38.9 -8.7,75.8 -24.2,108.8l107.2,107.2c22.2,22.2 22.2,58.3 0,80.5 -22.2,22.2 -58.3,22.2 -80.5,0l-97.4,-97.4c-44,35.7 -100.2,57.1 -161.3,57.1V476.4c0,-11.8 -9.5,-21.4 -21.4,-21.4h-42.7c-11.8,0 -21.4,9.5 -21.4,21.4v434.2c-61.1,0 -117.3,-21.4 -161.3,-57.1l-97.4,97.4c-22.2,22.2 -58.3,22.2 -80.5,0 -22.2,-22.2 -22.2,-58.3 0,-80.5L237.2,763.2C221.7,730.2 213,693.3 213,654.4V625.9H114.5c-31,0 -57.2,-24.3 -58.1,-55.3 -0.9,-32.2 25,-58.6 56.9,-58.6h99.7V407.4l-83,-83c-22.2,-22.2 -22.2,-58.3 0,-80.5 22.2,-22.2 58.3,-22.2 80.5,0l97.3,97.3H716.2L813.5,243.9c22.2,-22.2 58.3,-22.2 80.5,0 22.2,22.2 22.2,58.3 0,80.5l-83,83v104.6h99.7c31.9,0 57.8,26.3 56.9,58.6zM513.8,56.4c-110.1,0 -199.3,89.3 -199.3,199.3H713.1c0,-110.1 -89.3,-199.3 -199.3,-199.3z"
|
||||
android:strokeWidth="0.889835" />
|
||||
</vector>
|
13
module-app/src/main/res/drawable/ic_preference.xml
Normal file
13
module-app/src/main/res/drawable/ic_preference.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M44,16C44,22.627 38.627,28 32,28C29.973,28 28.064,27.497 26.39,26.61L9,44L4,39L21.39,21.61C20.503,19.936 20,18.027 20,16C20,9.373 25.373,4 32,4C34.027,4 35.936,4.502 37.61,5.39L30,13L35,18L42.61,10.39C43.498,12.064 44,13.973 44,16Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
9
module-app/src/main/res/drawable/ic_print.xml
Normal file
9
module-app/src/main/res/drawable/ic_print.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M851.2,957.9L179.2,957.9c-61.9,0 -110.9,-49.1 -110.9,-110.9L68.3,454.4c0,-32 25.6,-55.5 55.5,-55.5h55.5v224c0,32 25.6,55.5 55.5,55.5h558.9c32,0 55.5,-25.6 55.5,-55.5L849.1,398.9h55.5c32,0 55.5,25.6 55.5,55.5v390.4c2.1,64 -46.9,113.1 -108.8,113.1zM738.1,622.9h-448c-32,0 -55.5,-25.6 -55.5,-55.5v-448C234.7,89.6 260.3,64 290.1,64h448c32,0 55.5,25.6 55.5,55.5v448c0,29.9 -23.5,55.5 -55.5,55.5zM657.1,232.5L377.6,232.5c-19.2,0 -32,19.2 -32,32s12.8,25.6 32,32h279.5c19.2,0 32,-12.8 32,-32s-19.2,-32 -32,-32zM657.1,398.9L377.6,398.9c-19.2,0 -32,12.8 -32,32 0,12.8 19.2,25.6 32,25.6h279.5c19.2,0 32,-12.8 32,-25.6 0,-19.2 -19.2,-32 -32,-32z" />
|
||||
</vector>
|
9
module-app/src/main/res/drawable/ic_refresh.xml
Normal file
9
module-app/src/main/res/drawable/ic_refresh.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:pathData="M712.9,295.1c-120.7,-110.7 -308.3,-102.6 -419,18.1 -87.4,95.3 -103,236.3 -38.5,348.4 82,141.7 263.2,190.3 405,108.4 66.8,-38.6 116,-101.7 137,-176 8.9,-31.5 41.7,-49.8 73.2,-40.9 31.5,8.9 49.8,41.7 40.9,73.2C849.1,846.8 619.8,975 399.2,912.6 178.7,850.2 50.5,620.9 112.9,400.3S404.6,51.6 625.2,114c64.2,18.2 123.1,51.5 171.6,97.3l79.7,-79.7c11.6,-11.6 30.3,-11.6 41.9,-0.1 5.6,5.6 8.7,13.1 8.7,21V407c0,16.4 -13.3,29.6 -29.6,29.6H642.9c-16.4,0 -29.6,-13.3 -29.6,-29.7 0,-7.8 3.1,-15.3 8.6,-20.9l91,-90.9z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
30
module-app/src/main/res/drawable/ic_restart.xml
Normal file
30
module-app/src/main/res/drawable/ic_restart.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="4800"
|
||||
android:viewportHeight="4800">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M2270.9,56.1L2527.1,56.1A200,307.5 0,0 1,2727.1 363.6L2727.1,1098.6A200,307.5 0,0 1,2527.1 1406.1L2270.9,1406.1A200,307.5 0,0 1,2070.9 1098.6L2070.9,363.6A200,307.5 0,0 1,2270.9 56.1z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M2270.9,3406.1L2527.1,3406.1A200,307.5 0,0 1,2727.1 3713.6L2727.1,4448.6A200,307.5 0,0 1,2527.1 4756.1L2270.9,4756.1A200,307.5 0,0 1,2070.9 4448.6L2070.9,3713.6A200,307.5 0,0 1,2270.9 3406.1z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M646.7,835L827.8,653.9A200,307.5 135,0 1,1186.7 729.9L1706.4,1249.6A200,307.5 135,0 1,1782.4 1608.5L1601.3,1789.6A307.5,200 45,0 1,1242.4 1713.6L722.7,1193.9A307.5,200 45,0 1,646.7 835z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M3015.5,3203.8L3196.7,3022.7A200,307.5 135,0 1,3555.5 3098.7L4075.2,3618.4A307.5,200 45,0 1,4151.3 3977.3L3970.1,4158.4A307.5,200 45,0 1,3611.3 4082.4L3091.5,3562.7A307.5,200 45,0 1,3015.5 3203.8z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M49,2534.2L49,2278A200,307.5 90,0 1,356.5 2078L1091.5,2078A200,307.5 90,0 1,1399 2278L1399,2534.2A200,307.5 90,0 1,1091.5 2734.2L356.5,2734.2A200,307.5 90,0 1,49 2534.2z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M3399,2534.2L3399,2278.1A200,307.5 90,0 1,3706.5 2078.1L4441.5,2078.1A200,307.5 90,0 1,4749 2278.1L4749,2534.2A200,307.5 90,0 1,4441.5 2734.2L3706.5,2734.2A200,307.5 90,0 1,3399 2534.2z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M827.8,4158.4L646.7,3977.3A307.5,200 135,0 1,722.7 3618.4L1242.4,3098.7A200,307.5 45,0 1,1601.3 3022.7L1782.4,3203.8A200,307.5 45,0 1,1706.4 3562.7L1186.7,4082.4A200,307.5 45,0 1,827.8 4158.4z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M3196.7,1789.6L3015.5,1608.4A307.5,200 135,0 1,3091.5 1249.6L3611.3,729.9A307.5,200 135,0 1,3970.1 653.9L4151.3,835A200,307.5 45,0 1,4075.2 1193.9L3555.5,1713.6A200,307.5 45,0 1,3196.7 1789.6z" />
|
||||
</vector>
|
9
module-app/src/main/res/drawable/ic_share.xml
Normal file
9
module-app/src/main/res/drawable/ic_share.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M579.2,736.9l-218,-118.9a149.3,149.3 0,1 1,0 -212l218,-118.9a149.3,149.3 0,1 1,40.9 74.9l-218,118.9a149.9,149.9 0,0 1,0 62.2l218,118.9a149.3,149.3 0,1 1,-40.9 74.9z" />
|
||||
</vector>
|
9
module-app/src/main/res/drawable/ic_statistics.xml
Normal file
9
module-app/src/main/res/drawable/ic_statistics.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M446.2,566.7h369.3c-2.9,103.8 -39.4,190.7 -109.4,260.7s-156.6,106.4 -259.9,109.4c-103.8,-2.9 -190.7,-39.4 -260.7,-109.4s-106.4,-156.9 -109.4,-260.7c2.9,-103.2 39.4,-189.9 109.4,-259.9S342.4,200.4 446.2,197.5v369.2zM829.5,183.5c70,70 106.4,156.9 109.4,260.7L568.7,444.2L568.7,74.1c103.9,2.9 190.8,39.4 260.8,109.4z" />
|
||||
</vector>
|
9
module-app/src/main/res/drawable/ic_success.xml
Normal file
9
module-app/src/main/res/drawable/ic_success.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1008.7"
|
||||
android:viewportHeight="1008.7">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M504.4,0C226.3,0 0,226.3 0,504.4 0,782.5 226.3,1008.7 504.4,1008.7c278.1,0 504.4,-226.3 504.4,-504.4C1008.7,226.3 782.5,0 504.4,0ZM786.6,407.7 L458.6,743.9c-7.8,8 -18.6,12.6 -29.8,12.6h-0.2c-11.1,0 -21.8,-4.4 -29.7,-12.3L222.5,567.9c-16.4,-16.4 -16.4,-43 0,-59.4 16.4,-16.4 43,-16.4 59.4,0L428.2,654.8 726.5,348.9c16.3,-16.6 42.9,-16.9 59.4,-0.7 16.6,16.2 16.9,42.8 0.7,59.4z" />
|
||||
</vector>
|
34
module-app/src/main/res/drawable/ic_theme.xml
Normal file
34
module-app/src/main/res/drawable/ic_theme.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M37,37C39.209,37 41,35.209 41,33C41,31.527 39.667,29.527 37,27C34.333,29.527 33,31.527 33,33C33,35.209 34.791,37 37,37Z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M20.854,5.504L24.389,9.04"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M23.682,8.333L8.125,23.889L19.439,35.203L34.995,19.646L23.682,8.333Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M12,20.073L28.961,25.65"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M4,43H44"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round" />
|
||||
</vector>
|
9
module-app/src/main/res/drawable/ic_warn.xml
Normal file
9
module-app/src/main/res/drawable/ic_warn.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="m512,794a44.8,44.8 0,1 1,44.8 -44.8,44.8 44.8,0 0,1 -44.8,44.8zM471.9,230.8a40.1,40.1 0,0 1,80.2 0v369.1a40.1,40.1 0,0 1,-79.8 0zM512,0A512,512 0,1 0,1024 512,512 512,0 0,0 512,0Z" />
|
||||
</vector>
|
129
module-app/src/main/res/layout/activitiy_logger.xml
Normal file
129
module-app/src/main/res/layout/activitiy_logger.xml
Normal file
@@ -0,0 +1,129 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/colorThemeBackground"
|
||||
android:orientation="vertical"
|
||||
tools:context=".ui.activity.debug.LoggerActivity"
|
||||
tools:ignore="ContentDescription,UseCompoundDrawables,UnusedAttribute">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="0dp"
|
||||
android:gravity="center|start"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingTop="13dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingBottom="5dp">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/title_back_icon"
|
||||
style="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:src="@drawable/ic_back"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/back" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="2.5dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|start"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/debug_logs"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="19sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_count_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:text="@string/this_contents_clear_when_restarts_tip"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="11.5sp"
|
||||
android:tooltipText="@string/this_contents_clear_when_restarts_tip" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="true"
|
||||
android:gravity="center|end"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/refresh_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@drawable/ic_refresh"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/refresh" />
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/filter_icon"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:src="@drawable/ic_filter"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/filter_by_condition" />
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/export_all_icon"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:src="@drawable/ic_export"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/export_all" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="10dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/list_no_data_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:text="@string/no_list_data"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="17sp" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/list_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:divider="@color/trans"
|
||||
android:dividerHeight="5dp"
|
||||
android:fadingEdgeLength="10dp"
|
||||
android:listSelector="@color/trans"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:requiresFadingEdge="vertical"
|
||||
android:scrollbars="none"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
499
module-app/src/main/res/layout/activity_app_errors_detail.xml
Normal file
499
module-app/src/main/res/layout/activity_app_errors_detail.xml
Normal file
@@ -0,0 +1,499 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/root_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/colorThemeBackground"
|
||||
android:orientation="vertical"
|
||||
tools:context=".ui.activity.errors.AppErrorsDetailActivity"
|
||||
tools:ignore="ContentDescription,UseCompoundDrawables,SmallSp,UnusedAttribute">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="0dp"
|
||||
android:gravity="center|start"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingTop="15dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingBottom="15dp">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/title_back_icon"
|
||||
style="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:src="@drawable/ic_back"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/back" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detail_title_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="2.5dp"
|
||||
android:layout_weight="1"
|
||||
android:singleLine="true"
|
||||
android:text="@string/app_name"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="19sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/print_icon"
|
||||
android:layout_width="23dp"
|
||||
android:layout_height="23dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@drawable/ic_print"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/print_to_logcat" />
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/copy_icon"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@drawable/ic_copy"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/copy_error_stack" />
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/export_icon"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:src="@drawable/ic_export"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/export_to_file" />
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/share_icon"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:src="@drawable/ic_share"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/share_error_stack" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/app_panel_scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:fadingEdgeLength="10dp"
|
||||
android:fillViewport="true"
|
||||
android:requiresFadingEdge="vertical"
|
||||
android:scrollbars="none">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/app_info_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:background="@drawable/bg_permotion_ripple"
|
||||
android:gravity="center|start"
|
||||
android:padding="10dp">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="45dp"
|
||||
app:cardBackgroundColor="@color/trans"
|
||||
app:cardCornerRadius="10dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/app_icon"
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="45dp" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|start"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_name_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_user_id_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/bg_blue_round"
|
||||
android:ellipsize="end"
|
||||
android:paddingLeft="3dp"
|
||||
android:paddingTop="0.5dp"
|
||||
android:paddingRight="3dp"
|
||||
android:paddingBottom="0.5dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/user_id"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="9sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="13dp"
|
||||
android:layout_height="13dp"
|
||||
android:src="@drawable/ic_exception"
|
||||
app:tint="#FFEF5350" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_cpu_abi_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/bg_black_round"
|
||||
android:ellipsize="end"
|
||||
android:paddingLeft="3dp"
|
||||
android:paddingTop="0.5dp"
|
||||
android:paddingRight="3dp"
|
||||
android:paddingBottom="0.5dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/no_cpu_abi"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="9sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_version_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/error_type_icon"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_marginEnd="10dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:background="@drawable/bg_permotion_round"
|
||||
android:gravity="center|start"
|
||||
android:orientation="vertical"
|
||||
android:padding="15dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:gravity="center|start|top"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:text="@string/error_info"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error_info_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:lineSpacingExtra="5dp"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/jvm_error_panel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:gravity="center|start"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:text="@string/error_type"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error_type_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="#FFEF5350"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:text="@string/error_file_name"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error_file_name_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:text="@string/error_throw_class"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error_throw_class_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:text="@string/error_throw_method"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error_throw_method_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:text="@string/error_line_number"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error_line_number_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:text="@string/error_record_time"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error_record_time_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<com.fankes.apperrorstracking.ui.widget.MaterialSwitch
|
||||
android:id="@+id/disable_auto_wrap_error_stack_trace_switch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/bg_permotion_round"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:text="@string/disable_auto_wrap_error_stack_trace_content"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/error_stack_trace_scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:background="@drawable/bg_stack_round"
|
||||
android:fadingEdgeLength="10dp"
|
||||
android:fillViewport="true"
|
||||
android:overScrollMode="never"
|
||||
android:padding="15dp"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
android:scrollbars="none"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error_stack_trace_movable_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:lineSpacingExtra="5dp"
|
||||
android:textColor="#B65B57"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="12sp"
|
||||
android:typeface="monospace" />
|
||||
</HorizontalScrollView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error_stack_trace_fixed_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:background="@drawable/bg_stack_round"
|
||||
android:lineSpacingExtra="5dp"
|
||||
android:padding="15dp"
|
||||
android:textColor="#B65B57"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="12sp"
|
||||
android:typeface="monospace" />
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</LinearLayout>
|
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
85
module-app/src/main/res/layout/activity_app_errors_muted.xml
Normal file
85
module-app/src/main/res/layout/activity_app_errors_muted.xml
Normal file
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/colorThemeBackground"
|
||||
android:orientation="vertical"
|
||||
tools:context=".ui.activity.errors.AppErrorsRecordActivity"
|
||||
tools:ignore="ContentDescription,UseCompoundDrawables,UnusedAttribute">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="0dp"
|
||||
android:gravity="center|start"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingTop="15dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingBottom="15dp">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/title_back_icon"
|
||||
style="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:src="@drawable/ic_back"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/back" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="2.5dp"
|
||||
android:layout_weight="1"
|
||||
android:singleLine="true"
|
||||
android:text="@string/muted_errors_apps"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="19sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/unmute_all_icon"
|
||||
android:layout_width="26dp"
|
||||
android:layout_height="26dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:src="@drawable/ic_clear"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/unmute_all"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="5dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/list_no_data_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:text="@string/no_list_data"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="17sp" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/list_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:divider="@color/trans"
|
||||
android:dividerHeight="15dp"
|
||||
android:fadingEdgeLength="10dp"
|
||||
android:listSelector="@color/trans"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:requiresFadingEdge="vertical"
|
||||
android:scrollbars="none"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
132
module-app/src/main/res/layout/activity_app_errors_record.xml
Normal file
132
module-app/src/main/res/layout/activity_app_errors_record.xml
Normal file
@@ -0,0 +1,132 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/colorThemeBackground"
|
||||
android:orientation="vertical"
|
||||
tools:context=".ui.activity.errors.AppErrorsRecordActivity"
|
||||
tools:ignore="ContentDescription,UseCompoundDrawables,UnusedAttribute">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="0dp"
|
||||
android:gravity="center|start"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingTop="13dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingBottom="5dp">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/title_back_icon"
|
||||
style="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:src="@drawable/ic_back"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/back" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="2.5dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|start"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/errors_record"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="19sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_count_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:text="@string/loading"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="11.5sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/app_error_sis_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@drawable/ic_statistics"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/app_errors_statistics"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/clear_all_icon"
|
||||
android:layout_width="26dp"
|
||||
android:layout_height="26dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@drawable/ic_clear"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/clear_all"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/export_all_icon"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:src="@drawable/ic_export"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/export_all"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="10dp">
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/list_progress_view"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
app:trackCornerRadius="10dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/list_no_data_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:text="@string/no_list_data"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="17sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/list_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:divider="@color/trans"
|
||||
android:dividerHeight="15dp"
|
||||
android:fadingEdgeLength="10dp"
|
||||
android:listSelector="@color/trans"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:requiresFadingEdge="vertical"
|
||||
android:scrollbars="none"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
133
module-app/src/main/res/layout/activity_config.xml
Normal file
133
module-app/src/main/res/layout/activity_config.xml
Normal file
@@ -0,0 +1,133 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/colorThemeBackground"
|
||||
android:orientation="vertical"
|
||||
tools:context=".ui.activity.main.ConfigureActivity"
|
||||
tools:ignore="ContentDescription,UseCompoundDrawables,UnusedAttribute">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="0dp"
|
||||
android:gravity="center|start"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingTop="13dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingBottom="5dp">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/title_back_icon"
|
||||
style="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:src="@drawable/ic_back"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/back" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="2.5dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|start"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/apps_config_template"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="19sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_count_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:text="@string/loading"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="11.5sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/global_icon"
|
||||
android:layout_width="23dp"
|
||||
android:layout_height="23dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@drawable/ic_global"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/global_config"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/batch_icon"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@drawable/ic_batch"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/batch_operations"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/filter_icon"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:src="@drawable/ic_filter"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/filter_by_condition"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="10dp"
|
||||
android:animateLayoutChanges="true">
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/list_progress_view"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
app:trackCornerRadius="10dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/list_no_data_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:text="@string/no_list_result"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="17sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/list_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:divider="@color/trans"
|
||||
android:dividerHeight="15dp"
|
||||
android:fadingEdgeLength="10dp"
|
||||
android:listSelector="@color/trans"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:requiresFadingEdge="vertical"
|
||||
android:scrollbars="none"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
733
module-app/src/main/res/layout/activity_main.xml
Normal file
733
module-app/src/main/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,733 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/colorThemeBackground"
|
||||
android:orientation="vertical"
|
||||
tools:context=".ui.activity.main.MainActivity"
|
||||
tools:ignore="UseCompoundDrawables,ContentDescription,SmallSp,UnusedAttribute">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="0dp"
|
||||
android:gravity="center|start"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingTop="13dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingBottom="5dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:singleLine="true"
|
||||
android:text="@string/app_name"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="25sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/title_logger_icon"
|
||||
style="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:alpha="0.85"
|
||||
android:padding="0.5dp"
|
||||
android:src="@drawable/ic_debug"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/debug_logs" />
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/title_restart_icon"
|
||||
style="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:layout_width="26dp"
|
||||
android:layout_height="26dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:alpha="0.85"
|
||||
android:src="@drawable/ic_restart"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/restart_system" />
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/title_github_icon"
|
||||
style="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:layout_width="27dp"
|
||||
android:layout_height="27dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:alpha="0.85"
|
||||
android:src="@drawable/ic_github"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/project_address" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/main_lin_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:background="@drawable/bg_dark_round"
|
||||
android:elevation="0dp"
|
||||
android:gravity="center">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:id="@+id/main_img_status"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_marginStart="25dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:src="@drawable/ic_warn"
|
||||
android:tint="@color/white" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingRight="20dp"
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/main_text_status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:text="@string/module_not_activated"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/main_text_version"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.8"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:text="@string/module_version"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/main_text_release_version"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:background="@drawable/bg_orange_round"
|
||||
android:ellipsize="end"
|
||||
android:paddingLeft="5dp"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingRight="5dp"
|
||||
android:paddingBottom="2dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="11sp"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/main_text_system_version"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.8"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:text="@string/system_version"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/main_text_api_way"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:alpha="0.6"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="11sp"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:fadingEdgeLength="10dp"
|
||||
android:fillViewport="true"
|
||||
android:requiresFadingEdge="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:animateLayoutChanges="true"
|
||||
android:background="@drawable/bg_permotion_round"
|
||||
android:elevation="0dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="15dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:gravity="center|start">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="15dp"
|
||||
android:layout_height="15dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
app:cardBackgroundColor="#009688"
|
||||
app:cardCornerRadius="50dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:padding="2.5dp"
|
||||
android:src="@drawable/ic_preference" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.85"
|
||||
android:singleLine="true"
|
||||
android:text="@string/preference_settings"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.fankes.apperrorstracking.ui.widget.MaterialSwitch
|
||||
android:id="@+id/only_show_errors_in_front_switch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:text="@string/only_show_errors_in_front"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:alpha="0.6"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:text="@string/only_show_errors_in_front_tip"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<com.fankes.apperrorstracking.ui.widget.MaterialSwitch
|
||||
android:id="@+id/only_show_errors_in_main_process_switch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:text="@string/only_show_errors_in_main_process"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:alpha="0.6"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:text="@string/only_show_errors_in_main_process_tip"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<com.fankes.apperrorstracking.ui.widget.MaterialSwitch
|
||||
android:id="@+id/always_shows_reopen_app_options_switch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:text="@string/errors_dialog_always_show_reopen"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:alpha="0.6"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:text="@string/errors_dialog_always_show_reopen_tip"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<com.fankes.apperrorstracking.ui.widget.MaterialSwitch
|
||||
android:id="@+id/enable_apps_configs_template_switch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:text="@string/enable_apps_config_template"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mgr_apps_configs_template_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="13dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginRight="13dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:background="@drawable/bg_button_round"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/mgr_apps_config_template"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:alpha="0.6"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:text="@string/apps_config_template_tip"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginTop="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:background="@drawable/bg_permotion_round"
|
||||
android:elevation="0dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingTop="15dp"
|
||||
android:paddingRight="15dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|start">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="15dp"
|
||||
android:layout_height="15dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
app:cardBackgroundColor="#BA68C8"
|
||||
app:cardCornerRadius="50dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:padding="2.5dp"
|
||||
android:src="@drawable/ic_theme" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.85"
|
||||
android:singleLine="true"
|
||||
android:text="@string/theme_settings"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.fankes.apperrorstracking.ui.widget.MaterialSwitch
|
||||
android:id="@+id/enable_material3_app_errors_dialog_switch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/enable_md3_app_errors_dialog"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:alpha="0.6"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:text="@string/enable_md3_app_errors_dialog_tip"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginTop="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:background="@drawable/bg_permotion_round"
|
||||
android:elevation="0dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="15dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:gravity="center|start">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="15dp"
|
||||
android:layout_height="15dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
app:cardBackgroundColor="#2196F3"
|
||||
app:cardCornerRadius="50dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:padding="2.5dp"
|
||||
android:src="@drawable/ic_function" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.85"
|
||||
android:singleLine="true"
|
||||
android:text="@string/function_mgr"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/view_errors_record_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="13dp"
|
||||
android:layout_marginRight="13dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/bg_button_round"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/view_errors_record"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:alpha="0.6"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:text="@string/view_errors_record_tip"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/view_muted_errors_apps_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="13dp"
|
||||
android:layout_marginRight="13dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/bg_button_round"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/view_muted_errors_apps"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:alpha="0.6"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:text="@string/view_muted_errors_apps_tip"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginTop="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:background="@drawable/bg_permotion_round"
|
||||
android:elevation="0dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingTop="15dp"
|
||||
android:paddingRight="15dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|start">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="15dp"
|
||||
android:layout_height="15dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
app:cardBackgroundColor="#FFFF9800"
|
||||
app:cardCornerRadius="50dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:padding="2.5dp"
|
||||
android:src="@drawable/ic_home" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.85"
|
||||
android:singleLine="true"
|
||||
android:text="@string/display_settings"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.fankes.apperrorstracking.ui.widget.MaterialSwitch
|
||||
android:id="@+id/hide_icon_in_launcher_switch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/hide_app_icon_on_launcher"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:alpha="0.6"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:text="@string/hide_app_icon_on_launcher_tip"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:alpha="0.6"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:text="@string/hide_app_icon_on_launcher_notice"
|
||||
android:textColor="#FF5722"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/app_analytics_config_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginTop="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:background="@drawable/bg_permotion_round"
|
||||
android:elevation="0dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingTop="15dp"
|
||||
android:paddingRight="15dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|start">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="15dp"
|
||||
android:layout_height="15dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
app:cardBackgroundColor="#FFCB2E62"
|
||||
app:cardCornerRadius="50dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:padding="2dp"
|
||||
android:src="@drawable/ic_appcenter" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.85"
|
||||
android:singleLine="true"
|
||||
android:text="@string/microsoft_app_center"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.fankes.apperrorstracking.ui.widget.MaterialSwitch
|
||||
android:id="@+id/enable_anonymous_statistics_switch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/enable_anonymous_statistics"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:alpha="0.6"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:text="@string/enable_anonymous_statistics_tip"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/payment_following_zh_cn_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginTop="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:background="@drawable/bg_permotion_round"
|
||||
android:elevation="0dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingRight="15dp"
|
||||
tools:ignore="HardcodedText">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/link_with_follow_me"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:text="恰饭时间\n点击前往酷安关注我,获取我的更多应用"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
app:cardCornerRadius="15dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:src="@mipmap/bg_payment_code" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:text="开发者 酷安 @星夜不荟\n未经允许不得转载、修改复制我的劳动成果"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginTop="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/bg_permotion_round"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:src="@mipmap/ic_yukihookapi" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoLink="web"
|
||||
android:ellipsize="end"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:maxLines="2"
|
||||
android:text="@string/about_module"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</LinearLayout>
|
67
module-app/src/main/res/layout/adapter_app_errors_muted.xml
Normal file
67
module-app/src/main/res/layout/adapter_app_errors_muted.xml
Normal file
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_permotion_ripple"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp"
|
||||
tools:ignore="ContentDescription,UseCompoundDrawables,SmallSp">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="45dp"
|
||||
app:cardBackgroundColor="@color/trans"
|
||||
app:cardCornerRadius="10dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/app_icon"
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="45dp" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|start"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_name_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mute_type_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/unmute_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_button_round"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/unmute"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
127
module-app/src/main/res/layout/adapter_app_errors_record.xml
Normal file
127
module-app/src/main/res/layout/adapter_app_errors_record.xml
Normal file
@@ -0,0 +1,127 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_permotion_ripple"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp"
|
||||
tools:ignore="ContentDescription,UseCompoundDrawables,SmallSp">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="45dp"
|
||||
app:cardBackgroundColor="@color/trans"
|
||||
app:cardCornerRadius="10dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/app_icon"
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="45dp" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|start"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.75"
|
||||
android:gravity="center|start">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_name_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_user_id_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/bg_blue_round"
|
||||
android:ellipsize="end"
|
||||
android:paddingLeft="3dp"
|
||||
android:paddingTop="0.5dp"
|
||||
android:paddingRight="3dp"
|
||||
android:paddingBottom="0.5dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/user_id"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="9sp"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/errors_time_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_weight="0.25"
|
||||
android:alpha="0.85"
|
||||
android:ellipsize="start"
|
||||
android:gravity="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error_type_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/bg_red_round"
|
||||
android:ellipsize="end"
|
||||
android:paddingLeft="3dp"
|
||||
android:paddingTop="0.5dp"
|
||||
android:paddingRight="3dp"
|
||||
android:paddingBottom="0.5dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="9sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error_msg_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/error_type_icon"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_marginEnd="10dp" />
|
||||
</LinearLayout>
|
54
module-app/src/main/res/layout/adapter_app_info.xml
Normal file
54
module-app/src/main/res/layout/adapter_app_info.xml
Normal file
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_permotion_ripple"
|
||||
android:gravity="center|start"
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp"
|
||||
tools:ignore="ContentDescription,UseCompoundDrawables,SmallSp">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="45dp"
|
||||
app:cardBackgroundColor="@color/trans"
|
||||
app:cardCornerRadius="10dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/app_icon"
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="45dp" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:gravity="center|start"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_name_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/config_type_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
53
module-app/src/main/res/layout/adapter_logger.xml
Normal file
53
module-app/src/main/res/layout/adapter_logger.xml
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_permotion_round"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/priority_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:padding="5dp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="13sp"
|
||||
android:typeface="monospace" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center|start"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/message_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:lineSpacingExtra="3dp"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/time_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="11sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/throwable_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:lineSpacingExtra="3dp"
|
||||
android:textColor="#EF5350"
|
||||
android:textSize="11sp"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
66
module-app/src/main/res/layout/dia_app_config.xml
Normal file
66
module-app/src/main/res/layout/dia_app_config.xml
Normal file
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingTop="15dp"
|
||||
android:paddingRight="15dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8.5dp"
|
||||
android:layout_marginRight="8.5dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:text="@string/when_errors_how_to_show_tip"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<RadioGroup
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.radiobutton.MaterialRadioButton
|
||||
android:id="@+id/config_radio_0"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:singleLine="true"
|
||||
android:text="@string/follow_global_config"
|
||||
app:buttonTint="@color/colorPrimaryAccent" />
|
||||
|
||||
<com.google.android.material.radiobutton.MaterialRadioButton
|
||||
android:id="@+id/config_radio_1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:text="@string/show_errors_dialog"
|
||||
app:buttonTint="@color/colorPrimaryAccent" />
|
||||
|
||||
<com.google.android.material.radiobutton.MaterialRadioButton
|
||||
android:id="@+id/config_radio_2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:text="@string/show_errors_notify"
|
||||
app:buttonTint="@color/colorPrimaryAccent" />
|
||||
|
||||
<com.google.android.material.radiobutton.MaterialRadioButton
|
||||
android:id="@+id/config_radio_3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:text="@string/show_errors_toast"
|
||||
app:buttonTint="@color/colorPrimaryAccent" />
|
||||
|
||||
<com.google.android.material.radiobutton.MaterialRadioButton
|
||||
android:id="@+id/config_radio_4"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:text="@string/show_nothing"
|
||||
app:buttonTint="@color/colorPrimaryAccent" />
|
||||
</RadioGroup>
|
||||
</LinearLayout>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user