Improve background loop exception presentation

This commit is contained in:
Jonas Lochmann 2024-07-29 02:00:00 +02:00
parent 931b884567
commit bb0decc808
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
13 changed files with 149 additions and 25 deletions

View file

@ -122,6 +122,14 @@
</intent-filter>
</activity>
<activity
android:name=".ui.diagnose.exception.DiagnoseExceptionActivity"
android:theme="@style/AppTheme.Translucent"
android:exported="false"
android:excludeFromRecents="true"
android:taskAffinity=":exception"
android:launchMode="singleTop" />
<!-- system integration -->
<receiver android:name=".integration.platform.android.receiver.BootReceiver" android:exported="false">

View file

@ -220,7 +220,8 @@ data class AppStatusMessage(
val title: String,
val text: String,
val subtext: String? = null,
val showSwitchToDefaultUserOption: Boolean = false
val showSwitchToDefaultUserOption: Boolean = false,
val showErrorMessage: Boolean = false
): Parcelable
data class BatteryStatus(

View file

@ -1,5 +1,5 @@
/*
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* TimeLimit Copyright <C> 2019 - 2024 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
@ -26,6 +26,7 @@ import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.sync.actions.SignOutAtDeviceAction
import io.timelimit.android.sync.actions.apply.ApplyActionUtil
import io.timelimit.android.ui.MainActivity
import io.timelimit.android.ui.diagnose.exception.DiagnoseExceptionActivity
class BackgroundActionService: Service() {
companion object {
@ -36,7 +37,7 @@ class BackgroundActionService: Service() {
fun prepareRevokeTemporarilyAllowed(context: Context) = Intent(context, BackgroundActionService::class.java)
.putExtra(ACTION, ACTION_REVOKE_TEMPORARILY_ALLOWED_APPS)
fun getSwitchToDefaultUserIntent(context: Context) = PendingIntent.getService(
fun getSwitchToDefaultUserIntent(context: Context): PendingIntent = PendingIntent.getService(
context,
PendingIntentIds.SWITCH_TO_DEFAULT_USER,
Intent(context, BackgroundActionService::class.java)
@ -44,12 +45,22 @@ class BackgroundActionService: Service() {
PendingIntentIds.PENDING_INTENT_FLAGS
)
fun getOpenAppIntent(context: Context) = PendingIntent.getActivity(
fun getOpenAppIntent(context: Context): PendingIntent = PendingIntent.getActivity(
context,
PendingIntentIds.OPEN_MAIN_APP,
Intent(context, MainActivity::class.java),
PendingIntentIds.PENDING_INTENT_FLAGS
)
fun getOpenAppWithErrorIntent(context: Context): PendingIntent = PendingIntent.getActivities(
context,
PendingIntentIds.OPEN_MAIN_APP_WITH_ERROR,
arrayOf(
Intent(context, MainActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
Intent(context, DiagnoseExceptionActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
),
PendingIntentIds.PENDING_INTENT_FLAGS
)
}
private val notificationManager: NotificationManager by lazy {

View file

@ -84,7 +84,12 @@ class BackgroundService: Service() {
.setContentTitle(appStatusMessage.title)
.setContentText(appStatusMessage.text)
.setSubText(appStatusMessage.subtext)
.setContentIntent(BackgroundActionService.getOpenAppIntent(context))
.setContentIntent(
if (appStatusMessage.showErrorMessage)
BackgroundActionService.getOpenAppWithErrorIntent(context)
else
BackgroundActionService.getOpenAppIntent(context)
)
.setWhen(0)
.setShowWhen(false)
.setSound(null)

View file

@ -29,6 +29,7 @@ object NotificationIds {
const val REVOKE_TEMPORARILY_ALLOWED_APPS = 3
const val TIME_WARNING = 4
const val EXTRA_TIME_STARTED = 5
const val OPEN_MAIN_APP_WITH_ERROR = 6
}
object NotificationChannels {
@ -133,6 +134,7 @@ object PendingIntentIds {
const val SWITCH_TO_DEFAULT_USER = 3
const val U2F_NFC_DISCOVERY = 4
const val U2F_USB_RESPONSE = 5
const val OPEN_MAIN_APP_WITH_ERROR = 6
val PENDING_INTENT_FLAGS = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE

View file

@ -777,7 +777,8 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
appLogic.platformIntegration.setAppStatusMessage(AppStatusMessage(
appLogic.context.getString(R.string.background_logic_error),
appLogic.context.getString(R.string.background_logic_error_internal),
showSwitchToDefaultUserOption = deviceRelatedData.canSwitchToDefaultUser
showSwitchToDefaultUserOption = deviceRelatedData.canSwitchToDefaultUser,
showErrorMessage = true
))
appLogic.platformIntegration.setShowBlockingOverlay(false)
}

View file

@ -1,5 +1,5 @@
/*
* Open TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* Open TimeLimit Copyright <C> 2019 - 2024 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
@ -29,6 +29,7 @@ import io.timelimit.android.extensions.safeNavigate
import io.timelimit.android.livedata.liveDataFromNonNullValue
import io.timelimit.android.livedata.liveDataFromNullableValue
import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.ui.diagnose.exception.DiagnoseExceptionDialogFragment
import io.timelimit.android.ui.main.ActivityViewModelHolder
import io.timelimit.android.ui.main.AuthenticationFab
import io.timelimit.android.ui.main.FragmentWithCustomTitle

View file

@ -0,0 +1,34 @@
/*
* TimeLimit Copyright <C> 2019 - 2024 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.exception
import android.os.Bundle
import androidx.fragment.app.FragmentActivity
import io.timelimit.android.logic.DefaultAppLogic
class DiagnoseExceptionActivity: FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val ex = DefaultAppLogic.with(this).backgroundTaskLogic.lastLoopException.value
if (ex != null) {
if (savedInstanceState == null) {
DiagnoseExceptionDialogFragment.newInstance(ex, true).show(supportFragmentManager)
}
} else finish()
}
}

View file

@ -1,5 +1,5 @@
/*
* TimeLimit Copyright <C> 2019 Jonas Lochmann
* TimeLimit Copyright <C> 2019 - 2024 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
@ -13,12 +13,13 @@
* 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
package io.timelimit.android.ui.diagnose.exception
import android.app.Dialog
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.DialogInterface
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
@ -26,35 +27,26 @@ import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import io.timelimit.android.R
import io.timelimit.android.extensions.showSafe
import java.io.PrintWriter
import java.io.StringWriter
class DiagnoseExceptionDialogFragment: DialogFragment() {
companion object {
private const val DIALOG_TAG = "DiagnoseExceptionDialogFragment"
private const val EXCEPTION = "ex"
private const val FINISH_ON_DISMISS = "finish"
fun newInstance(exception: Exception) = DiagnoseExceptionDialogFragment().apply {
fun newInstance(exception: Exception, finishOnDismiss: Boolean = false) = DiagnoseExceptionDialogFragment().apply {
arguments = Bundle().apply {
putSerializable(EXCEPTION, exception)
putBoolean(FINISH_ON_DISMISS, finishOnDismiss)
}
}
fun getStackTraceString(tr: Throwable): String = StringWriter().let { sw ->
PrintWriter(sw).let { pw ->
tr.printStackTrace(pw)
pw.flush()
}
sw.toString()
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val message = getStackTraceString(arguments!!.getSerializable(EXCEPTION) as Exception)
val clipboard = context!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val message = ExceptionUtil.formatInterpreted(requireContext(), requireArguments().getSerializable(EXCEPTION) as Exception)
val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
return AlertDialog.Builder(context!!, theme)
return AlertDialog.Builder(requireContext(), theme)
.setMessage(message)
.setNeutralButton(R.string.diagnose_copy_to_clipboard) { _, _ ->
clipboard.setPrimaryClip(ClipData.newPlainText("TimeLimit", message))
@ -65,5 +57,13 @@ class DiagnoseExceptionDialogFragment: DialogFragment() {
.create()
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
if (requireArguments().getBoolean(FINISH_ON_DISMISS) && isResumed) {
requireActivity().finish()
}
}
fun show(fragmentManager: FragmentManager) = showSafe(fragmentManager, DIALOG_TAG)
}

View file

@ -0,0 +1,45 @@
/*
* TimeLimit Copyright <C> 2019 - 2024 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.exception
import android.content.Context
import io.timelimit.android.R
import io.timelimit.android.integration.platform.android.foregroundapp.InstanceIdForegroundAppHelper.InstanceIdException
import java.io.PrintWriter
import java.io.StringWriter
object ExceptionUtil {
fun formatInterpreted(context: Context, tr: Throwable): String {
val explain = when (tr) {
is InstanceIdException.EventsNotSortedByTimestamp -> context.getString(R.string.background_logic_errpr_detailed_instanceid_sorting)
else -> null
}
val tr2 = formatSimple(tr)
return if (explain != null) "$explain\n\n$tr2"
else tr2
}
private fun formatSimple(tr: Throwable): String = StringWriter().let { sw ->
PrintWriter(sw).let { pw ->
tr.printStackTrace(pw)
pw.flush()
}
sw.toString()
}
}

View file

@ -123,6 +123,12 @@
<string name="background_logic_opening_lockscreen">Sperrbildschirm wird geöffnet</string>
<string name="background_logic_permission_sanction_title">Berechtigung fehlt</string>
<string name="background_logic_permission_sanction_text">nehme an, dass alles verwendet wird</string>
<string name="background_logic_errpr_detailed_instanceid_sorting">Es gibt ein Problem mit der
Nutzungsstatisitkdatenbank Ihres Gerätes.
Deshalb kann TimeLimit nicht zuverlässig erkennen, welche App verwendet wird.
Dieses Problem kann nur behoben werden durch vier Tagen Geduld oder das Zurücksetzen des Gerätes.
Eine Neuinstallation von TimeLimit hilft NICHT.
</string>
<string name="background_logic_temporarily_allowed_title">Apps wurden vorübergehend erlaubt</string>
<string name="background_logic_temporarily_allowed_text">Zum Rückgängig machen hier tippen oder Bildschirm ausschalten</string>

View file

@ -154,6 +154,12 @@
<string name="background_logic_opening_lockscreen">Opening lock screen</string>
<string name="background_logic_permission_sanction_title">Missing permission</string>
<string name="background_logic_permission_sanction_text">assuming that everything is used</string>
<string name="background_logic_errpr_detailed_instanceid_sorting">There is an issue with
the data in the usage stats database of your device.
Due to that, TimeLimit can not reliable detect the running Apps.
You can do nothing to solve this except waiting for four days or doing a reset of the whole device.
Reinstalling TimeLimit does NOT help.
</string>
<string name="background_logic_temporarily_allowed_title">Apps are temporarily allowed</string>
<string name="background_logic_temporarily_allowed_text">Tap here or turn screen off to undo</string>

View file

@ -1,5 +1,5 @@
<!--
Open TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
Open TimeLimit Copyright <C> 2019 - 2024 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
@ -34,6 +34,10 @@
<item name="colorAccent">@color/white</item>
</style>
<style name="AppTheme.Translucent" parent="AppTheme">
<item name="android:windowIsFloating">true</item>
</style>
<!-- from https://stackoverflow.com/a/46286184 -->
<style name="BottomSheetDialog" parent="Theme.MaterialComponents.DayNight.BottomSheetDialog">
<item name="colorPrimary">@color/colorPrimary</item>