Additional debug info in application logs

pull/3670/head
Alex Baker 6 months ago
parent f4e0d519d7
commit bbac4da7d0

@ -112,6 +112,20 @@ class TasksJsonExporter @Inject constructor(
} }
} }
suspend fun doSettingsExport(os: OutputStream?) = withContext(Dispatchers.IO) {
val writer = os!!.bufferedWriter()
with (JsonWriter(writer)) {
write("{")
write("version", BuildConfig.VERSION_CODE)
write("timestamp", currentTimeMillis())
write("\"data\":{")
writePreferences()
write("}")
write("}")
}
writer.flush()
}
@Throws(IOException::class) @Throws(IOException::class)
private suspend fun doTasksExport(os: OutputStream?, taskIds: List<Long>) = withContext(Dispatchers.IO) { private suspend fun doTasksExport(os: OutputStream?, taskIds: List<Long>) = withContext(Dispatchers.IO) {
val writer = os!!.bufferedWriter() val writer = os!!.bufferedWriter()
@ -146,11 +160,7 @@ class TasksJsonExporter @Inject constructor(
write("caldavCalendars", caldavDao.getCalendars()) write("caldavCalendars", caldavDao.getCalendars())
write("taskListMetadata", taskListMetadataDao.getAll()) write("taskListMetadata", taskListMetadataDao.getAll())
write("taskAttachments", taskAttachmentDao.getAttachments()) write("taskAttachments", taskAttachmentDao.getAttachments())
write("intPrefs", preferences.getPrefs(Integer::class.java)) writePreferences()
write("longPrefs", preferences.getPrefs(java.lang.Long::class.java))
write("stringPrefs", preferences.getPrefs(String::class.java))
write("boolPrefs", preferences.getPrefs(java.lang.Boolean::class.java))
write("setPrefs", preferences.getPrefs(Set::class.java) as Map<String, Set<String>>, lastItem = true)
write("}") write("}")
write("}") write("}")
} }
@ -159,6 +169,14 @@ class TasksJsonExporter @Inject constructor(
exportCount = taskIds.size exportCount = taskIds.size
} }
private fun JsonWriter.writePreferences() {
write("intPrefs", preferences.getPrefs(Integer::class.java))
write("longPrefs", preferences.getPrefs(java.lang.Long::class.java))
write("stringPrefs", preferences.getPrefs(String::class.java))
write("boolPrefs", preferences.getPrefs(java.lang.Boolean::class.java))
write("setPrefs", preferences.getPrefs(Set::class.java) as Map<String, Set<String>>, lastItem = true)
}
private fun onFinishExport(outputFile: String) = post { private fun onFinishExport(outputFile: String) = post {
context?.toast( context?.toast(
R.string.export_toast, R.string.export_toast,

@ -4,12 +4,14 @@ import android.annotation.SuppressLint
import android.app.Application import android.app.Application
import android.os.Process import android.os.Process
import android.util.Log import android.util.Log
import dagger.Lazy
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.tasks.BuildConfig import org.tasks.BuildConfig
import org.tasks.backup.TasksJsonExporter
import org.tasks.logging.LogFormatter.Companion.LINE_SEPARATOR import org.tasks.logging.LogFormatter.Companion.LINE_SEPARATOR
import org.tasks.preferences.Device import org.tasks.preferences.Device
import timber.log.Timber import timber.log.Timber
@ -27,6 +29,8 @@ import javax.inject.Singleton
@Singleton @Singleton
class FileLogger @Inject constructor( class FileLogger @Inject constructor(
private val context: Application, private val context: Application,
private val device: Lazy<Device>,
private val tasksJsonExporter: Lazy<TasksJsonExporter>,
) : Timber.DebugTree() { ) : Timber.DebugTree() {
private val logDirectory = File(context.cacheDir, "logs").apply { mkdirs() } private val logDirectory = File(context.cacheDir, "logs").apply { mkdirs() }
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
@ -82,7 +86,10 @@ class FileLogger @Inject constructor(
Timber.e(e, "Failed to save logcat") Timber.e(e, "Failed to save logcat")
} }
zos.putNextEntry(ZipEntry("device.txt")) zos.putNextEntry(ZipEntry("device.txt"))
zos.write(Device(context).debugInfo.toByteArray()) zos.write(device.get().debugInfo.toByteArray())
zos.closeEntry()
zos.putNextEntry(ZipEntry("settings.json"))
tasksJsonExporter.get().doSettingsExport(zos)
zos.closeEntry() zos.closeEntry()
fileHandler.flush() fileHandler.flush()
logDirectory logDirectory

@ -1,77 +0,0 @@
package org.tasks.preferences;
import static java.util.Arrays.asList;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Build;
import android.speech.RecognizerIntent;
import com.google.common.base.Joiner;
import org.tasks.BuildConfig;
import java.util.List;
import javax.inject.Inject;
import dagger.hilt.android.qualifiers.ApplicationContext;
import timber.log.Timber;
public class Device {
private final Context context;
@Inject
public Device(@ApplicationContext Context context) {
this.context = context;
}
public boolean hasCamera() {
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
}
public boolean hasMicrophone() {
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE);
}
public boolean voiceInputAvailable() {
PackageManager pm = context.getPackageManager();
List<ResolveInfo> activities =
pm.queryIntentActivities(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0);
return (activities.size() != 0);
}
public String getDebugInfo() {
try {
return Joiner.on("\n")
.join(
asList(
"",
"----------",
"Tasks: "
+ BuildConfig.VERSION_NAME
+ " ("
+ BuildConfig.FLAVOR
+ " build "
+ BuildConfig.VERSION_CODE
+ ")",
"Android: " + Build.VERSION.RELEASE + " (" + Build.DISPLAY + ")",
"Locale: " + java.util.Locale.getDefault(),
"Model: " + Build.MANUFACTURER + " " + Build.MODEL,
"Product: " + Build.PRODUCT + " (" + Build.DEVICE + ")",
"Kernel: "
+ System.getProperty("os.version")
+ " ("
+ Build.VERSION.INCREMENTAL
+ ")",
"----------",
""));
} catch (Exception e) {
Timber.e(e);
}
return "";
}
}

@ -0,0 +1,47 @@
package org.tasks.preferences
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.speech.RecognizerIntent
import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.BuildConfig
import java.util.Locale
import javax.inject.Inject
class Device @Inject constructor(
@ApplicationContext private val context: Context,
private val permissionChecker: PermissionChecker,
) {
@SuppressLint("UnsupportedChromeOsCameraSystemFeature")
fun hasCamera() = context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
fun hasMicrophone() = context.packageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
fun voiceInputAvailable(): Boolean {
val pm = context.packageManager
val activities =
pm.queryIntentActivities(Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0)
return (activities.size != 0)
}
val debugInfo: String
get() = """
----------
Tasks: ${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR} build ${BuildConfig.VERSION_CODE})
Android: ${Build.VERSION.RELEASE} (${Build.DISPLAY})
Locale: ${Locale.getDefault()}
Model: ${Build.MANUFACTURER} ${Build.MODEL}
Product: ${Build.PRODUCT} (${Build.DEVICE})
Kernel: ${System.getProperty("os.version")} (${Build.VERSION.INCREMENTAL})
----------
notifications: ${permissionChecker.hasNotificationPermission()}
reminders: ${permissionChecker.hasAlarmsAndRemindersPermission()}
background location: ${permissionChecker.canAccessBackgroundLocation()}
foreground location: ${permissionChecker.canAccessForegroundLocation()}
calendar: ${permissionChecker.canAccessCalendars()}
----------
""".trimIndent()
}

@ -41,9 +41,12 @@ public class PermissionChecker {
return !atLeastTiramisu() || checkPermissions(permission.POST_NOTIFICATIONS); return !atLeastTiramisu() || checkPermissions(permission.POST_NOTIFICATIONS);
} }
public boolean hasAlarmsAndRemindersPermission() {
return org.tasks.extensions.Context.INSTANCE.canScheduleExactAlarms(context);
}
public boolean canNotify() { public boolean canNotify() {
return org.tasks.extensions.Context.INSTANCE.canScheduleExactAlarms(context) return hasAlarmsAndRemindersPermission() && hasNotificationPermission();
&& hasNotificationPermission();
} }
private boolean checkPermissions(String... permissions) { private boolean checkPermissions(String... permissions) {

@ -220,7 +220,7 @@ class TaskListViewModel @Inject constructor(
fun dismissBanner(tookAction: Boolean = false) { fun dismissBanner(tookAction: Boolean = false) {
when (state.value.banner) { when (state.value.banner) {
Banner.NotificationsDisabled -> preferences.warnNotificationsDisabled = tookAction Banner.NotificationsDisabled -> preferences.warnNotificationsDisabled = tookAction
Banner.AlarmsDisabled -> preferences.warnAlarmsDisabled = false Banner.AlarmsDisabled -> preferences.warnAlarmsDisabled = tookAction
Banner.QuietHoursEnabled -> preferences.warnQuietHoursDisabled = false Banner.QuietHoursEnabled -> preferences.warnQuietHoursDisabled = false
Banner.BegForMoney -> { Banner.BegForMoney -> {
preferences.lastSubscribeRequest = currentTimeMillis() preferences.lastSubscribeRequest = currentTimeMillis()

Loading…
Cancel
Save