Add exit reason screen

This commit is contained in:
Jonas Lochmann 2022-02-28 01:00:00 +01:00
parent 7edb636c67
commit c3b9efa37a
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
13 changed files with 338 additions and 5 deletions

View file

@ -0,0 +1,73 @@
/*
* TimeLimit Copyright <C> 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 <https://www.gnu.org/licenses/>.
*/
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
}
}

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -86,6 +86,8 @@ abstract class PlatformIntegration(
confirmationLevel: SystemPermissionConfirmationLevel = SystemPermissionConfirmationLevel.None confirmationLevel: SystemPermissionConfirmationLevel = SystemPermissionConfirmationLevel.None
): Boolean ): Boolean
abstract fun getExitLog(): List<ExitLogItem>
var installedAppsChangeListener: Runnable? = null var installedAppsChangeListener: Runnable? = null
var systemClockChangeListener: Runnable? = null var systemClockChangeListener: Runnable? = null
} }

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * 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 * 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<ExitLogItem> {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
activityManager.getHistoricalProcessExitReasons(context.packageName, 0, 0)
.map { ExitLogItem.fromApplicationExitInfo(it) }
} else emptyList()
}
} }

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -185,4 +185,6 @@ class DummyIntegration(
permission: SystemPermission, permission: SystemPermission,
confirmationLevel: SystemPermissionConfirmationLevel confirmationLevel: SystemPermissionConfirmationLevel
): Boolean = false ): Boolean = false
override fun getExitLog(): List<ExitLogItem> = emptyList()
} }

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * 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 * 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( AuthenticationFab.manageAuthenticationFab(
fab = binding.fab, fab = binding.fab,
shouldHighlight = auth.shouldHighlightAuthenticationButton, shouldHighlight = auth.shouldHighlightAuthenticationButton,

View file

@ -0,0 +1,46 @@
/*
* TimeLimit Copyright <C> 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 <https://www.gnu.org/licenses/>.
*/
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<DiagnoseExitReasonHolder>() {
var content: List<ExitLogItem> 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)

View file

@ -0,0 +1,48 @@
/*
* TimeLimit Copyright <C> 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 <https://www.gnu.org/licenses/>.
*/
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<String?> = liveDataFromNullableValue("${getString(R.string.diagnose_er_title)} < ${getString(
R.string.about_diagnose_title)} < ${getString(R.string.main_tab_overview)}")
}

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
TimeLimit Copyright <C> 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 <https://www.gnu.org/licenses/>.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="isEmpty"
type="boolean" />
<import type="android.view.View" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:visibility="@{isEmpty ? View.VISIBLE : View.GONE}"
android:textAppearance="?android:textAppearanceMedium"
android:padding="16dp"
android:gravity="center"
android:text="@string/diagnose_er_empty"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</layout>

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
TimeLimit Copyright <C> 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 <https://www.gnu.org/licenses/>.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="timeString"
type="String" />
<variable
name="summaryString"
type="String" />
<variable
name="detailString"
type="String" />
<import type="android.text.TextUtils" />
<import type="android.view.View" />
</data>
<androidx.cardview.widget.CardView
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
app:cardUseCompatPadding="true"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:padding="8dp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:textAppearance="?android:textAppearanceLarge"
tools:text="vom Benutzer beendet"
android:text="@{summaryString}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:textAppearance="?android:textAppearanceMedium"
tools:text="er will keine Begrenzungen"
android:text="@{detailString}"
android:visibility="@{TextUtils.isEmpty(detailString) ? View.GONE : View.VISIBLE}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:textAppearance="?android:textAppearanceMedium"
tools:text="vor fünf Minuten"
android:text="@{timeString}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</layout>

View file

@ -1,5 +1,5 @@
<!-- <!--
TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
the Free Software Foundation version 3 of the License. the Free Software Foundation version 3 of the License.
@ -87,6 +87,13 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<Button
style="?materialButtonOutlinedStyle"
android:id="@+id/diagnose_exit_reasons_button"
android:text="@string/diagnose_er_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View file

@ -405,6 +405,13 @@
app:exitAnim="@anim/nav_default_exit_anim" app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim" app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" /> app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<action
android:id="@+id/action_diagnoseMainFragment_to_diagnoseExitReasonFragment"
app:destination="@id/diagnoseExitReasonFragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/diagnoseClockFragment" android:id="@+id/diagnoseClockFragment"
@ -584,4 +591,8 @@
android:name="childId" android:name="childId"
app:argType="string" /> app:argType="string" />
</fragment> </fragment>
<fragment
android:id="@+id/diagnoseExitReasonFragment"
android:name="io.timelimit.android.ui.diagnose.exitreason.DiagnoseExitReasonFragment"
android:label="DiagnoseExitReasonFragment" />
</navigation> </navigation>

View file

@ -530,6 +530,9 @@
<string name="diagnose_don_no_owner_toast">TimeLimit ist KEIN Geräte-Besitzer</string> <string name="diagnose_don_no_owner_toast">TimeLimit ist KEIN Geräte-Besitzer</string>
<string name="diagnose_don_not_supported_toast">Ihr System ermöglicht es nicht, einen Organisationsnamen zu hinterlegen</string> <string name="diagnose_don_not_supported_toast">Ihr System ermöglicht es nicht, einen Organisationsnamen zu hinterlegen</string>
<string name="diagnose_er_title">Gründe für die Beendung</string>
<string name="diagnose_er_empty">es liegen keine Daten vor; diese Daten sind nur bei Android 11 und neuer verfügbar</string>
<string name="dont_ask_password_on_device_title">Passwortabfrage</string> <string name="dont_ask_password_on_device_title">Passwortabfrage</string>
<string name="dont_ask_password_on_device_text_enabled"> <string name="dont_ask_password_on_device_text_enabled">
Die Passwortabfrage ist an diesem Gerät aktiviert. Die Passwortabfrage ist an diesem Gerät aktiviert.

View file

@ -583,6 +583,9 @@
<string name="diagnose_don_no_owner_toast">TimeLimit is NOT set as device owner</string> <string name="diagnose_don_no_owner_toast">TimeLimit is NOT set as device owner</string>
<string name="diagnose_don_not_supported_toast">Your system does not support setting the organization name</string> <string name="diagnose_don_not_supported_toast">Your system does not support setting the organization name</string>
<string name="diagnose_er_title">Exit Reasons</string>
<string name="diagnose_er_empty">there is no data; this data is only available at Android 11 and newer</string>
<string name="dont_ask_password_on_device_title">Password prompts</string> <string name="dont_ask_password_on_device_title">Password prompts</string>
<string name="dont_ask_password_on_device_text_enabled"> <string name="dont_ask_password_on_device_text_enabled">
Password prompts are enabled at the device. Password prompts are enabled at the device.