Update file logging

* Add ability to send logs
* Log app exit info
* Redact sensitive info from logs
pull/3104/head
Alex Baker 1 year ago
parent c7d10f69e7
commit baadbef820

7
app/proguard.pro vendored

@ -2,13 +2,6 @@
-keep class org.tasks.** { *; } -keep class org.tasks.** { *; }
# remove logging statements
-assumenosideeffects class timber.log.Timber* {
public static *** v(...);
public static *** d(...);
public static *** i(...);
}
# guava # guava
-dontwarn sun.misc.Unsafe -dontwarn sun.misc.Unsafe
-dontwarn java.lang.ClassValue -dontwarn java.lang.ClassValue

@ -17,16 +17,16 @@ import leakcanary.AppWatcher
import org.tasks.logging.FileLogger import org.tasks.logging.FileLogger
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import timber.log.Timber import timber.log.Timber
import timber.log.Timber.DebugTree
import javax.inject.Inject import javax.inject.Inject
class BuildSetup @Inject constructor( class BuildSetup @Inject constructor(
private val context: Application, private val context: Application,
private val preferences: Preferences private val preferences: Preferences,
private val fileLogger: FileLogger,
) { ) {
fun setup() { fun setup() {
Timber.plant(DebugTree()) Timber.plant(Timber.DebugTree())
Timber.plant(FileLogger(context)) Timber.plant(fileLogger)
SoLoader.init(context, false) SoLoader.init(context, false)
if (preferences.getBoolean(R.string.p_leakcanary, false)) { if (preferences.getBoolean(R.string.p_leakcanary, false)) {
AppWatcher.manualInstall(context) AppWatcher.manualInstall(context)

@ -1,5 +1,6 @@
package org.tasks package org.tasks
import android.app.ActivityManager
import android.app.Application import android.app.Application
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
@ -13,6 +14,7 @@ import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.coroutineScope import androidx.lifecycle.coroutineScope
import androidx.work.Configuration import androidx.work.Configuration
import com.mikepenz.iconics.Iconics import com.mikepenz.iconics.Iconics
import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.astrid.service.Upgrader import com.todoroo.astrid.service.Upgrader
import dagger.Lazy import dagger.Lazy
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
@ -85,6 +87,11 @@ class Tasks : Application(), Configuration.Provider {
val lastVersion = preferences.lastSetVersion val lastVersion = preferences.lastSetVersion
val currentVersion = BuildConfig.VERSION_CODE val currentVersion = BuildConfig.VERSION_CODE
Timber.i("Astrid Startup. %s => %s", lastVersion, currentVersion) Timber.i("Astrid Startup. %s => %s", lastVersion, currentVersion)
if (AndroidUtilities.atLeastR()) {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val exitReasons = activityManager.getHistoricalProcessExitReasons(null, 0, 5)
Timber.i(exitReasons.joinToString("\n"))
}
// invoke upgrade service // invoke upgrade service
if (lastVersion != currentVersion) { if (lastVersion != currentVersion) {

@ -161,7 +161,6 @@ class CaldavSynchronizer @Inject constructor(
caldavDao.update(account) caldavDao.update(account)
} }
val urls = resources.map { it.href.toString() }.toHashSet() val urls = resources.map { it.href.toString() }.toHashSet()
Timber.d("Found calendars: %s", urls)
for (calendar in caldavDao.findDeletedCalendars(account.uuid!!, ArrayList(urls))) { for (calendar in caldavDao.findDeletedCalendars(account.uuid!!, ArrayList(urls))) {
taskDeleter.delete(calendar) taskDeleter.delete(calendar)
} }
@ -239,7 +238,7 @@ class CaldavSynchronizer @Inject constructor(
val httpUrl = resource.href val httpUrl = resource.href
val remoteCtag = resource.ctag val remoteCtag = resource.ctag
if (caldavCalendar.ctag?.equals(remoteCtag) == true) { if (caldavCalendar.ctag?.equals(remoteCtag) == true) {
Timber.d("%s up to date", caldavCalendar.name) Timber.d("up to date: $caldavCalendar")
return return
} }
Timber.d("updating $caldavCalendar") Timber.d("updating $caldavCalendar")

@ -1,29 +1,46 @@
package org.tasks.logging package org.tasks.logging
import android.content.Context import android.annotation.SuppressLint
import android.app.Application
import android.os.Process import android.os.Process
import android.util.Log import android.util.Log
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 org.tasks.logging.LogFormatter.Companion.LINE_SEPARATOR import org.tasks.logging.LogFormatter.Companion.LINE_SEPARATOR
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.util.logging.FileHandler import java.util.logging.FileHandler
import java.util.logging.Logger import java.util.logging.Logger
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import javax.inject.Inject
import javax.inject.Singleton
class FileLogger( @Singleton
context: Context, class FileLogger @Inject constructor(
private val context: Application,
) : Timber.DebugTree() { ) : Timber.DebugTree() {
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)
private val fileHandler: FileHandler = FileHandler( private val fileHandler: FileHandler = FileHandler(
"${logDirectory.absolutePath}/log.%g.txt", "${logDirectory.absolutePath}/log.%g.txt",
20 * 1024 * 1024, 20 * 1024 * 1024,
10 10
) )
private val logger = Logger.getLogger(context.packageName) @SuppressLint("LogNotTimber")
private val logger = Logger.getLogger(context.packageName).apply {
try {
useParentHandlers = false
} catch (e: SecurityException) {
Log.e("FileLogger", "Failed to disable parent handlers", e)
}
}
init { init {
fileHandler.formatter = LogFormatter() fileHandler.formatter = LogFormatter()
@ -41,8 +58,43 @@ class FileLogger(
} }
} }
fun flush() { suspend fun getZipFile(): File = withContext(Dispatchers.IO) {
fileHandler.flush() val zipFile = File(context.cacheDir, "logs.zip")
val buffer = ByteArray(1024)
FileOutputStream(zipFile).use { fos ->
ZipOutputStream(fos).use { zos ->
try {
Runtime
.getRuntime()
.exec(arrayOf("logcat", "-d", "-v", "threadtime", "*:*"))
?.inputStream
?.use { logcat ->
zos.putNextEntry(ZipEntry("logcat.txt"))
var len: Int
while (logcat.read(buffer).also { len = it } > 0) {
zos.write(buffer, 0, len)
}
zos.closeEntry()
}
} catch (e: IOException) {
Timber.e(e, "Failed to save logcat")
}
fileHandler.flush()
logDirectory
.listFiles { _, name -> name?.endsWith(".txt") == true }
?.forEach { file ->
FileInputStream(file).use { fis ->
zos.putNextEntry(ZipEntry(file.name))
var len: Int
while (fis.read(buffer).also { len = it } > 0) {
zos.write(buffer, 0, len)
}
zos.closeEntry()
}
}
}
}
zipFile
} }
companion object { companion object {

@ -3,14 +3,19 @@ package org.tasks.preferences.fragments
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.core.content.FileProvider
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.lifecycleScope
import com.todoroo.astrid.utility.Constants
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.tasks.BuildConfig import org.tasks.BuildConfig
import org.tasks.R import org.tasks.R
import org.tasks.Tasks.Companion.IS_GENERIC import org.tasks.Tasks.Companion.IS_GENERIC
import org.tasks.analytics.Firebase import org.tasks.analytics.Firebase
import org.tasks.dialogs.WhatsNewDialog import org.tasks.dialogs.WhatsNewDialog
import org.tasks.injection.InjectingPreferenceFragment import org.tasks.injection.InjectingPreferenceFragment
import org.tasks.logging.FileLogger
import javax.inject.Inject import javax.inject.Inject
private const val FRAG_TAG_WHATS_NEW = "frag_tag_whats_new" private const val FRAG_TAG_WHATS_NEW = "frag_tag_whats_new"
@ -19,6 +24,7 @@ private const val FRAG_TAG_WHATS_NEW = "frag_tag_whats_new"
class HelpAndFeedback : InjectingPreferenceFragment() { class HelpAndFeedback : InjectingPreferenceFragment() {
@Inject lateinit var firebase: Firebase @Inject lateinit var firebase: Firebase
@Inject lateinit var fileLogger: FileLogger
override fun getPreferenceXml() = R.xml.help_and_feedback override fun getPreferenceXml() = R.xml.help_and_feedback
@ -47,6 +53,26 @@ class HelpAndFeedback : InjectingPreferenceFragment() {
false false
} }
findPreference(R.string.send_application_logs)
.setOnPreferenceClickListener {
lifecycleScope.launch {
val file = FileProvider.getUriForFile(
requireContext(),
Constants.FILE_PROVIDER_AUTHORITY,
fileLogger.getZipFile()
)
val intent = Intent(Intent.ACTION_SEND)
.setType("message/rfc822")
.putExtra(Intent.EXTRA_EMAIL, arrayOf(getString(R.string.support_email)))
.putExtra(Intent.EXTRA_SUBJECT, "Tasks logs")
.putExtra(Intent.EXTRA_TEXT, device.debugInfo)
.putExtra(Intent.EXTRA_STREAM, file)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(intent)
}
false
}
findPreference(R.string.p_collect_statistics) findPreference(R.string.p_collect_statistics)
.setOnPreferenceClickListener { .setOnPreferenceClickListener {
showRestartDialog() showRestartDialog()

@ -320,6 +320,7 @@ File %1$s contained %2$s.\n\n
<string name="source_code">Source code</string> <string name="source_code">Source code</string>
<string name="translations">Contribute translations</string> <string name="translations">Contribute translations</string>
<string name="contact_developer">Contact developer</string> <string name="contact_developer">Contact developer</string>
<string name="send_application_logs">Send application logs</string>
<string name="rate_tasks">Rate Tasks</string> <string name="rate_tasks">Rate Tasks</string>
<string name="quiet_hours_summary">No reminders during quiet hours</string> <string name="quiet_hours_summary">No reminders during quiet hours</string>
<string name="TLA_menu_donate">Donate</string> <string name="TLA_menu_donate">Donate</string>

@ -30,6 +30,11 @@
android:title="@string/contact_developer" android:title="@string/contact_developer"
app:icon="@drawable/ic_outline_email_24px" /> app:icon="@drawable/ic_outline_email_24px" />
<Preference
android:key="@string/send_application_logs"
android:title="@string/send_application_logs"
app:icon="@drawable/ic_outline_attachment_24px" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory

@ -9,10 +9,11 @@ import javax.inject.Inject
class BuildSetup @Inject constructor( class BuildSetup @Inject constructor(
private val context: Application, private val context: Application,
private val fileLogger: FileLogger,
) { ) {
fun setup() { fun setup() {
Timber.plant(ErrorReportingTree()) Timber.plant(ErrorReportingTree())
Timber.plant(FileLogger(context)) Timber.plant(fileLogger)
} }
private class ErrorReportingTree : Timber.Tree() { private class ErrorReportingTree : Timber.Tree() {

@ -9,6 +9,7 @@ plugins {
alias(libs.plugins.ksp) apply false alias(libs.plugins.ksp) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false alias(libs.plugins.jetbrains.kotlin.android) apply false
alias(libs.plugins.protobuf) apply false alias(libs.plugins.protobuf) apply false
alias(libs.plugins.redacted) apply false
} }
buildscript { buildscript {

@ -8,6 +8,7 @@ plugins {
alias(libs.plugins.kotlin.parcelize) alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.ksp) alias(libs.plugins.ksp)
alias(libs.plugins.redacted)
} }
kotlin { kotlin {
@ -68,6 +69,11 @@ android {
} }
} }
redacted {
redactedAnnotation = "org/tasks/data/Redacted"
enabled = gradle.startParameter.taskNames.any { it.contains("Release") }
}
dependencies { dependencies {
ksp(libs.androidx.room.compiler) ksp(libs.androidx.room.compiler)
} }

@ -0,0 +1,5 @@
package org.tasks.data
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.PROPERTY)
annotation class Redacted

@ -7,6 +7,7 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import org.tasks.CommonParcelable import org.tasks.CommonParcelable
import org.tasks.CommonParcelize import org.tasks.CommonParcelize
import org.tasks.data.Redacted
import org.tasks.data.db.Table import org.tasks.data.db.Table
import java.net.HttpURLConnection import java.net.HttpURLConnection
@ -20,12 +21,16 @@ data class CaldavAccount(
val id: Long = 0, val id: Long = 0,
@ColumnInfo(name = "cda_uuid") @ColumnInfo(name = "cda_uuid")
val uuid: String? = Task.NO_UUID, val uuid: String? = Task.NO_UUID,
@Redacted
@ColumnInfo(name = "cda_name") @ColumnInfo(name = "cda_name")
var name: String? = "", var name: String? = "",
@Redacted
@ColumnInfo(name = "cda_url") @ColumnInfo(name = "cda_url")
var url: String? = "", var url: String? = "",
@Redacted
@ColumnInfo(name = "cda_username") @ColumnInfo(name = "cda_username")
var username: String? = "", var username: String? = "",
@Redacted
@ColumnInfo(name = "cda_password") @ColumnInfo(name = "cda_password")
@Transient @Transient
var password: String? = "", var password: String? = "",
@ -70,9 +75,6 @@ data class CaldavAccount(
fun isLoggedOut() = error?.startsWith(ERROR_UNAUTHORIZED) == true fun isLoggedOut() = error?.startsWith(ERROR_UNAUTHORIZED) == true
fun isPaymentRequired() = error.isPaymentRequired() fun isPaymentRequired() = error.isPaymentRequired()
override fun toString(): String {
return "CaldavAccount(id=$id, uuid=$uuid, name=$name, url=$url, username=$username, error=$error, accountType=$accountType, isCollapsed=$isCollapsed, serverType=$serverType)"
}
val hasError: Boolean val hasError: Boolean
get() = !error.isNullOrBlank() get() = !error.isNullOrBlank()

@ -8,6 +8,7 @@ import kotlinx.serialization.Transient
import org.tasks.CommonParcelable import org.tasks.CommonParcelable
import org.tasks.CommonParcelize import org.tasks.CommonParcelize
import org.tasks.data.NO_ORDER import org.tasks.data.NO_ORDER
import org.tasks.data.Redacted
import org.tasks.data.db.Table import org.tasks.data.db.Table
@Serializable @Serializable
@ -19,9 +20,11 @@ data class CaldavCalendar(
@ColumnInfo(name = "cdl_id") var id: Long = 0, @ColumnInfo(name = "cdl_id") var id: Long = 0,
@ColumnInfo(name = "cdl_account") val account: String? = Task.NO_UUID, @ColumnInfo(name = "cdl_account") val account: String? = Task.NO_UUID,
@ColumnInfo(name = "cdl_uuid") var uuid: String? = Task.NO_UUID, @ColumnInfo(name = "cdl_uuid") var uuid: String? = Task.NO_UUID,
@Redacted
@ColumnInfo(name = "cdl_name") var name: String? = "", @ColumnInfo(name = "cdl_name") var name: String? = "",
@ColumnInfo(name = "cdl_color") var color: Int = 0, @ColumnInfo(name = "cdl_color") var color: Int = 0,
@ColumnInfo(name = "cdl_ctag") var ctag: String? = null, @ColumnInfo(name = "cdl_ctag") var ctag: String? = null,
@Redacted
@ColumnInfo(name = "cdl_url") var url: String? = "", @ColumnInfo(name = "cdl_url") var url: String? = "",
@ColumnInfo(name = "cdl_icon") val icon: String? = null, @ColumnInfo(name = "cdl_icon") val icon: String? = null,
@ColumnInfo(name = "cdl_order") val order: Int = NO_ORDER, @ColumnInfo(name = "cdl_order") val order: Int = NO_ORDER,

@ -8,6 +8,7 @@ import kotlinx.serialization.Transient
import org.tasks.CommonParcelable import org.tasks.CommonParcelable
import org.tasks.CommonParcelize import org.tasks.CommonParcelize
import org.tasks.data.NO_ORDER import org.tasks.data.NO_ORDER
import org.tasks.data.Redacted
@Serializable @Serializable
@CommonParcelize @CommonParcelize
@ -17,6 +18,7 @@ data class Filter(
@ColumnInfo(name = "_id") @ColumnInfo(name = "_id")
@Transient @Transient
val id: Long = 0, val id: Long = 0,
@Redacted
@ColumnInfo(name = "title") @ColumnInfo(name = "title")
val title: String? = null, val title: String? = null,
@ColumnInfo(name = "sql") @ColumnInfo(name = "sql")

@ -10,6 +10,7 @@ import kotlinx.serialization.Transient
import org.tasks.CommonParcelable import org.tasks.CommonParcelable
import org.tasks.CommonParcelize import org.tasks.CommonParcelize
import org.tasks.data.NO_ORDER import org.tasks.data.NO_ORDER
import org.tasks.data.Redacted
import org.tasks.data.UUIDHelper import org.tasks.data.UUIDHelper
import org.tasks.data.db.Table import org.tasks.data.db.Table
import org.tasks.formatCoordinates import org.tasks.formatCoordinates
@ -30,16 +31,22 @@ data class Place(
val id: Long = 0, val id: Long = 0,
@ColumnInfo(name = "uid") @ColumnInfo(name = "uid")
val uid: String? = UUIDHelper.newUUID(), val uid: String? = UUIDHelper.newUUID(),
@Redacted
@ColumnInfo(name = "name") @ColumnInfo(name = "name")
val name: String? = null, val name: String? = null,
@Redacted
@ColumnInfo(name = "address") @ColumnInfo(name = "address")
val address: String? = null, val address: String? = null,
@Redacted
@ColumnInfo(name = "phone") @ColumnInfo(name = "phone")
val phone: String? = null, val phone: String? = null,
@Redacted
@ColumnInfo(name = "url") @ColumnInfo(name = "url")
val url: String? = null, val url: String? = null,
@Redacted
@ColumnInfo(name = "latitude") @ColumnInfo(name = "latitude")
val latitude: Double = 0.0, val latitude: Double = 0.0,
@Redacted
@ColumnInfo(name = "longitude") @ColumnInfo(name = "longitude")
val longitude: Double = 0.0, val longitude: Double = 0.0,
@ColumnInfo(name = "place_color") @ColumnInfo(name = "place_color")

@ -5,6 +5,7 @@ import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.Index import androidx.room.Index
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import org.tasks.data.Redacted
@Entity( @Entity(
tableName = "principals", tableName = "principals",
@ -22,8 +23,8 @@ data class Principal(
@PrimaryKey(autoGenerate = true) var id: Long = 0, @PrimaryKey(autoGenerate = true) var id: Long = 0,
val account: Long, val account: Long,
val href: String, val href: String,
var email: String? = null, @Redacted var email: String? = null,
@ColumnInfo(name = "display_name") var displayName: String? = null @Redacted @ColumnInfo(name = "display_name") var displayName: String? = null
) { ) {
val name: String val name: String
get() = displayName get() = displayName

@ -6,6 +6,7 @@ import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import org.tasks.data.Redacted
import org.tasks.data.db.Table import org.tasks.data.db.Table
@Serializable @Serializable
@ -28,6 +29,7 @@ data class Tag(
@ColumnInfo(name = "task", index = true) @ColumnInfo(name = "task", index = true)
@Transient @Transient
val task: Long = 0, val task: Long = 0,
@Redacted
@ColumnInfo(name = "name") @ColumnInfo(name = "name")
val name: String? = null, val name: String? = null,
@ColumnInfo(name = "tag_uid") @ColumnInfo(name = "tag_uid")

@ -8,6 +8,7 @@ import kotlinx.serialization.Transient
import org.tasks.CommonParcelable import org.tasks.CommonParcelable
import org.tasks.CommonParcelize import org.tasks.CommonParcelize
import org.tasks.data.NO_ORDER import org.tasks.data.NO_ORDER
import org.tasks.data.Redacted
import org.tasks.data.UUIDHelper import org.tasks.data.UUIDHelper
@CommonParcelize @CommonParcelize
@ -20,6 +21,7 @@ data class TagData(
val id: Long? = null, val id: Long? = null,
@ColumnInfo(name = "remoteId") @ColumnInfo(name = "remoteId")
val remoteId: String? = UUIDHelper.newUUID(), val remoteId: String? = UUIDHelper.newUUID(),
@Redacted
@ColumnInfo(name = "name") @ColumnInfo(name = "name")
val name: String? = "", val name: String? = "",
@ColumnInfo(name = "color") @ColumnInfo(name = "color")

@ -15,6 +15,7 @@ import kotlinx.serialization.json.JsonNames
import org.tasks.CommonParcelable import org.tasks.CommonParcelable
import org.tasks.CommonParcelize import org.tasks.CommonParcelize
import org.tasks.CommonRawValue import org.tasks.CommonRawValue
import org.tasks.data.Redacted
import org.tasks.data.UUIDHelper import org.tasks.data.UUIDHelper
import org.tasks.data.db.Table import org.tasks.data.db.Table
import org.tasks.data.sql.Field import org.tasks.data.sql.Field
@ -34,6 +35,7 @@ data class Task @OptIn(ExperimentalSerializationApi::class) constructor(
@ColumnInfo(name = "_id") @ColumnInfo(name = "_id")
@Transient @Transient
var id: Long = NO_ID, var id: Long = NO_ID,
@Redacted
@ColumnInfo(name = "title") @ColumnInfo(name = "title")
var title: String? = null, var title: String? = null,
@ColumnInfo(name = "importance") @ColumnInfo(name = "importance")
@ -50,6 +52,7 @@ data class Task @OptIn(ExperimentalSerializationApi::class) constructor(
var completionDate: Long = 0L, var completionDate: Long = 0L,
@ColumnInfo(name = "deleted") @ColumnInfo(name = "deleted")
var deletionDate: Long = 0L, var deletionDate: Long = 0L,
@Redacted
@ColumnInfo(name = "notes") @ColumnInfo(name = "notes")
var notes: String? = null, var notes: String? = null,
@ColumnInfo(name = "estimatedSeconds") @ColumnInfo(name = "estimatedSeconds")

@ -7,6 +7,7 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import org.tasks.CommonParcelable import org.tasks.CommonParcelable
import org.tasks.CommonParcelize import org.tasks.CommonParcelize
import org.tasks.data.Redacted
import org.tasks.data.UUIDHelper import org.tasks.data.UUIDHelper
@Serializable @Serializable
@ -19,6 +20,7 @@ data class TaskAttachment(
val id: Long? = null, val id: Long? = null,
@ColumnInfo(name = "file_uuid") @ColumnInfo(name = "file_uuid")
val remoteId: String = UUIDHelper.newUUID(), val remoteId: String = UUIDHelper.newUUID(),
@Redacted
@ColumnInfo(name = "filename") @ColumnInfo(name = "filename")
val name: String, val name: String,
@ColumnInfo(name = "uri") @ColumnInfo(name = "uri")

@ -7,6 +7,7 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import org.tasks.CommonParcelable import org.tasks.CommonParcelable
import org.tasks.CommonParcelize import org.tasks.CommonParcelize
import org.tasks.data.Redacted
import org.tasks.data.db.Table import org.tasks.data.db.Table
@Serializable @Serializable
@ -19,6 +20,7 @@ data class UserActivity(
var id: Long? = null, var id: Long? = null,
@ColumnInfo(name = "remoteId") @ColumnInfo(name = "remoteId")
var remoteId: String? = Task.NO_UUID, var remoteId: String? = Task.NO_UUID,
@Redacted
@ColumnInfo(name = "message") @ColumnInfo(name = "message")
var message: String? = "", var message: String? = "",
@ColumnInfo(name = "picture") @ColumnInfo(name = "picture")

@ -214,3 +214,4 @@ ksp = { id = "com.google.devtools.ksp", version = "2.0.21-1.0.26" }
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
protobuf = { id = "com.google.protobuf", version = "0.9.4" } protobuf = { id = "com.google.protobuf", version = "0.9.4" }
redacted = { id = "dev.zacsweers.redacted", version = "1.10.0" }
Loading…
Cancel
Save