diff --git a/app/src/main/java/io/timelimit/android/integration/platform/ExitLogItem.kt b/app/src/main/java/io/timelimit/android/integration/platform/ExitLogItem.kt new file mode 100644 index 0000000..6918b39 --- /dev/null +++ b/app/src/main/java/io/timelimit/android/integration/platform/ExitLogItem.kt @@ -0,0 +1,73 @@ +/* + * TimeLimit Copyright 2019 - 2022 Jonas Lochmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 3 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package io.timelimit.android.integration.platform + +import android.app.ApplicationExitInfo +import android.os.Build +import androidx.annotation.RequiresApi + +data class ExitLogItem ( + val timestamp: Long, + val reason: ExitReason, + val description: String? +) { + companion object { + @RequiresApi(Build.VERSION_CODES.R) + fun fromApplicationExitInfo(item: ApplicationExitInfo): ExitLogItem = ExitLogItem( + timestamp = item.timestamp, + reason = ExitReasonConverter.fromApplicationExitInfo(item), + description = item.description + ) + } +} + +enum class ExitReason { + AppNotResponding, + Crash, + CrashNative, + DependencyDied, + TooMuchRessourceUsage, + Self, + InitFailure, + LowMemory, + SystemOther, + PermissionChange, + Signaled, + SystemUnknown, + UserRequest, + UserStopped, + Unknown +} + +object ExitReasonConverter { + fun fromApplicationExitInfo(input: ApplicationExitInfo): ExitReason = when (input.reason) { + ApplicationExitInfo.REASON_ANR -> ExitReason.AppNotResponding + ApplicationExitInfo.REASON_CRASH -> ExitReason.Crash + ApplicationExitInfo.REASON_CRASH_NATIVE -> ExitReason.CrashNative + ApplicationExitInfo.REASON_DEPENDENCY_DIED -> ExitReason.DependencyDied + ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE -> ExitReason.TooMuchRessourceUsage + ApplicationExitInfo.REASON_EXIT_SELF -> ExitReason.Self + ApplicationExitInfo.REASON_INITIALIZATION_FAILURE -> ExitReason.InitFailure + ApplicationExitInfo.REASON_LOW_MEMORY -> ExitReason.LowMemory + ApplicationExitInfo.REASON_OTHER -> ExitReason.SystemOther + ApplicationExitInfo.REASON_PERMISSION_CHANGE -> ExitReason.PermissionChange + ApplicationExitInfo.REASON_SIGNALED -> ExitReason.Signaled + ApplicationExitInfo.REASON_UNKNOWN -> ExitReason.SystemUnknown + ApplicationExitInfo.REASON_USER_REQUESTED -> ExitReason.UserRequest + ApplicationExitInfo.REASON_USER_STOPPED -> ExitReason.UserStopped + else -> ExitReason.Unknown + } +} \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/integration/platform/PlatformIntegration.kt b/app/src/main/java/io/timelimit/android/integration/platform/PlatformIntegration.kt index a82cce8..00bd04b 100644 --- a/app/src/main/java/io/timelimit/android/integration/platform/PlatformIntegration.kt +++ b/app/src/main/java/io/timelimit/android/integration/platform/PlatformIntegration.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2021 Jonas Lochmann + * TimeLimit Copyright 2019 - 2022 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -86,6 +86,8 @@ abstract class PlatformIntegration( confirmationLevel: SystemPermissionConfirmationLevel = SystemPermissionConfirmationLevel.None ): Boolean + abstract fun getExitLog(): List + var installedAppsChangeListener: Runnable? = null var systemClockChangeListener: Runnable? = null } diff --git a/app/src/main/java/io/timelimit/android/integration/platform/android/AndroidIntegration.kt b/app/src/main/java/io/timelimit/android/integration/platform/android/AndroidIntegration.kt index 3cc2b0e..9b3619b 100644 --- a/app/src/main/java/io/timelimit/android/integration/platform/android/AndroidIntegration.kt +++ b/app/src/main/java/io/timelimit/android/integration/platform/android/AndroidIntegration.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2021 Jonas Lochmann + * TimeLimit Copyright 2019 - 2022 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -793,4 +793,11 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio } } } + + override fun getExitLog(): List { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + activityManager.getHistoricalProcessExitReasons(context.packageName, 0, 0) + .map { ExitLogItem.fromApplicationExitInfo(it) } + } else emptyList() + } } diff --git a/app/src/main/java/io/timelimit/android/integration/platform/dummy/DummyIntegration.kt b/app/src/main/java/io/timelimit/android/integration/platform/dummy/DummyIntegration.kt index 1186edd..6e675a9 100644 --- a/app/src/main/java/io/timelimit/android/integration/platform/dummy/DummyIntegration.kt +++ b/app/src/main/java/io/timelimit/android/integration/platform/dummy/DummyIntegration.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2021 Jonas Lochmann + * TimeLimit Copyright 2019 - 2022 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -185,4 +185,6 @@ class DummyIntegration( permission: SystemPermission, confirmationLevel: SystemPermissionConfirmationLevel ): Boolean = false + + override fun getExitLog(): List = emptyList() } diff --git a/app/src/main/java/io/timelimit/android/ui/diagnose/DiagnoseMainFragment.kt b/app/src/main/java/io/timelimit/android/ui/diagnose/DiagnoseMainFragment.kt index 4e9db52..15a0cd2 100644 --- a/app/src/main/java/io/timelimit/android/ui/diagnose/DiagnoseMainFragment.kt +++ b/app/src/main/java/io/timelimit/android/ui/diagnose/DiagnoseMainFragment.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2021 Jonas Lochmann + * TimeLimit Copyright 2019 - 2022 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -100,6 +100,13 @@ class DiagnoseMainFragment : Fragment(), FragmentWithCustomTitle { } } + binding.diagnoseExitReasonsButton.setOnClickListener { + navigation.safeNavigate( + DiagnoseMainFragmentDirections.actionDiagnoseMainFragmentToDiagnoseExitReasonFragment(), + R.id.diagnoseMainFragment + ) + } + AuthenticationFab.manageAuthenticationFab( fab = binding.fab, shouldHighlight = auth.shouldHighlightAuthenticationButton, diff --git a/app/src/main/java/io/timelimit/android/ui/diagnose/exitreason/DiagnoseExitReasonAdapter.kt b/app/src/main/java/io/timelimit/android/ui/diagnose/exitreason/DiagnoseExitReasonAdapter.kt new file mode 100644 index 0000000..67e8494 --- /dev/null +++ b/app/src/main/java/io/timelimit/android/ui/diagnose/exitreason/DiagnoseExitReasonAdapter.kt @@ -0,0 +1,46 @@ +/* + * TimeLimit Copyright 2019 - 2022 Jonas Lochmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 3 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package io.timelimit.android.ui.diagnose.exitreason + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.timelimit.android.databinding.DiagnoseExitReasonItemBinding +import io.timelimit.android.integration.platform.ExitLogItem +import java.text.DateFormat +import java.util.* +import kotlin.properties.Delegates + +class DiagnoseExitReasonAdapter: RecyclerView.Adapter() { + var content: List by Delegates.observable(emptyList()) { _, _, _ -> notifyDataSetChanged() } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DiagnoseExitReasonHolder = DiagnoseExitReasonHolder( + DiagnoseExitReasonItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: DiagnoseExitReasonHolder, position: Int) { + val item = content[position] + val view = holder.binding + + view.timeString = DateFormat.getDateTimeInstance().format(Date(item.timestamp)) + view.summaryString = item.reason.toString() + view.detailString = item.description + } + + override fun getItemCount(): Int = content.size +} + +class DiagnoseExitReasonHolder(val binding: DiagnoseExitReasonItemBinding): RecyclerView.ViewHolder(binding.root) \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/ui/diagnose/exitreason/DiagnoseExitReasonFragment.kt b/app/src/main/java/io/timelimit/android/ui/diagnose/exitreason/DiagnoseExitReasonFragment.kt new file mode 100644 index 0000000..d8ef0ce --- /dev/null +++ b/app/src/main/java/io/timelimit/android/ui/diagnose/exitreason/DiagnoseExitReasonFragment.kt @@ -0,0 +1,48 @@ +/* + * TimeLimit Copyright 2019 - 2022 Jonas Lochmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 3 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package io.timelimit.android.ui.diagnose.exitreason + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.LiveData +import androidx.recyclerview.widget.LinearLayoutManager +import io.timelimit.android.R +import io.timelimit.android.databinding.DiagnoseExitReasonFragmentBinding +import io.timelimit.android.livedata.liveDataFromNullableValue +import io.timelimit.android.logic.DefaultAppLogic +import io.timelimit.android.ui.main.FragmentWithCustomTitle + +class DiagnoseExitReasonFragment: Fragment(), FragmentWithCustomTitle { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val binding = DiagnoseExitReasonFragmentBinding.inflate(inflater, container, false) + val data = DefaultAppLogic.with(requireContext()).platformIntegration.getExitLog() + val recycler = binding.recycler + val adapter = DiagnoseExitReasonAdapter() + + adapter.content = data + recycler.layoutManager = LinearLayoutManager(requireContext()) + recycler.adapter = adapter + binding.isEmpty = data.isEmpty() + + return binding.root + } + + override fun getCustomTitle(): LiveData = liveDataFromNullableValue("${getString(R.string.diagnose_er_title)} < ${getString( + R.string.about_diagnose_title)} < ${getString(R.string.main_tab_overview)}") +} \ No newline at end of file diff --git a/app/src/main/res/layout/diagnose_exit_reason_fragment.xml b/app/src/main/res/layout/diagnose_exit_reason_fragment.xml new file mode 100644 index 0000000..cc9be20 --- /dev/null +++ b/app/src/main/res/layout/diagnose_exit_reason_fragment.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/diagnose_exit_reason_item.xml b/app/src/main/res/layout/diagnose_exit_reason_item.xml new file mode 100644 index 0000000..be7344e --- /dev/null +++ b/app/src/main/res/layout/diagnose_exit_reason_item.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_diagnose_main.xml b/app/src/main/res/layout/fragment_diagnose_main.xml index 1d49dd3..c162bcb 100644 --- a/app/src/main/res/layout/fragment_diagnose_main.xml +++ b/app/src/main/res/layout/fragment_diagnose_main.xml @@ -1,5 +1,5 @@