diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 84e544a..eb947aa 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -122,6 +122,14 @@ + + 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 6039007..07b5ca3 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 @@ -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( diff --git a/app/src/main/java/io/timelimit/android/integration/platform/android/BackgroundActionService.kt b/app/src/main/java/io/timelimit/android/integration/platform/android/BackgroundActionService.kt index b0e0bf9..925082b 100644 --- a/app/src/main/java/io/timelimit/android/integration/platform/android/BackgroundActionService.kt +++ b/app/src/main/java/io/timelimit/android/integration/platform/android/BackgroundActionService.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2022 Jonas Lochmann + * TimeLimit Copyright 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 { diff --git a/app/src/main/java/io/timelimit/android/integration/platform/android/BackgroundService.kt b/app/src/main/java/io/timelimit/android/integration/platform/android/BackgroundService.kt index 370c5db..43c83bc 100644 --- a/app/src/main/java/io/timelimit/android/integration/platform/android/BackgroundService.kt +++ b/app/src/main/java/io/timelimit/android/integration/platform/android/BackgroundService.kt @@ -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) diff --git a/app/src/main/java/io/timelimit/android/integration/platform/android/Notification.kt b/app/src/main/java/io/timelimit/android/integration/platform/android/Notification.kt index 761b754..44d1aea 100644 --- a/app/src/main/java/io/timelimit/android/integration/platform/android/Notification.kt +++ b/app/src/main/java/io/timelimit/android/integration/platform/android/Notification.kt @@ -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 diff --git a/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt b/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt index e80a192..76727a1 100644 --- a/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt +++ b/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt @@ -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) } 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 b4c7452..dc4bfd2 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 @@ /* - * Open TimeLimit Copyright 2019 - 2022 Jonas Lochmann + * Open TimeLimit Copyright 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 diff --git a/app/src/main/java/io/timelimit/android/ui/diagnose/exception/DiagnoseExceptionActivity.kt b/app/src/main/java/io/timelimit/android/ui/diagnose/exception/DiagnoseExceptionActivity.kt new file mode 100644 index 0000000..766e5b2 --- /dev/null +++ b/app/src/main/java/io/timelimit/android/ui/diagnose/exception/DiagnoseExceptionActivity.kt @@ -0,0 +1,34 @@ +/* + * TimeLimit Copyright 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 . + */ +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() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/ui/diagnose/DiagnoseExceptionDialogFragment.kt b/app/src/main/java/io/timelimit/android/ui/diagnose/exception/DiagnoseExceptionDialogFragment.kt similarity index 68% rename from app/src/main/java/io/timelimit/android/ui/diagnose/DiagnoseExceptionDialogFragment.kt rename to app/src/main/java/io/timelimit/android/ui/diagnose/exception/DiagnoseExceptionDialogFragment.kt index 7e8965f..e7fde69 100644 --- a/app/src/main/java/io/timelimit/android/ui/diagnose/DiagnoseExceptionDialogFragment.kt +++ b/app/src/main/java/io/timelimit/android/ui/diagnose/exception/DiagnoseExceptionDialogFragment.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 Jonas Lochmann + * TimeLimit Copyright 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 . */ -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) } \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/ui/diagnose/exception/ExceptionUtil.kt b/app/src/main/java/io/timelimit/android/ui/diagnose/exception/ExceptionUtil.kt new file mode 100644 index 0000000..14a2c67 --- /dev/null +++ b/app/src/main/java/io/timelimit/android/ui/diagnose/exception/ExceptionUtil.kt @@ -0,0 +1,45 @@ +/* + * TimeLimit Copyright 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 . + */ +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() + } +} \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index b375ce2..aeeae39 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -123,6 +123,12 @@ Sperrbildschirm wird geöffnet Berechtigung fehlt nehme an, dass alles verwendet wird + 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. + Apps wurden vorübergehend erlaubt Zum Rückgängig machen hier tippen oder Bildschirm ausschalten diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f8ff76d..1c523a9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -154,6 +154,12 @@ Opening lock screen Missing permission assuming that everything is used + 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. + Apps are temporarily allowed Tap here or turn screen off to undo diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 9f33201..87de540 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,5 +1,5 @@