Kaynağa Gözat

Add logic for logging start and end of important workers

Signed-off-by: Jonas Mayer <jonas.a.mayer@gmx.net>
Jonas Mayer 1 yıl önce
ebeveyn
işleme
5cc6322725

+ 12 - 0
app/src/main/java/com/nextcloud/client/di/ComponentsModule.java

@@ -23,8 +23,11 @@ package com.nextcloud.client.di;
 import com.nextcloud.client.documentscan.DocumentScanActivity;
 import com.nextcloud.client.editimage.EditImageActivity;
 import com.nextcloud.client.etm.EtmActivity;
+import com.nextcloud.client.etm.pages.EtmBackgroundJobsFragment;
 import com.nextcloud.client.files.downloader.FileTransferService;
+import com.nextcloud.client.jobs.BackgroundJobManagerImpl;
 import com.nextcloud.client.jobs.NotificationWork;
+import com.nextcloud.client.jobs.TestJob;
 import com.nextcloud.client.logger.ui.LogsActivity;
 import com.nextcloud.client.logger.ui.LogsViewModel;
 import com.nextcloud.client.media.PlayerService;
@@ -478,4 +481,13 @@ abstract class ComponentsModule {
 
     @ContributesAndroidInjector
     abstract ImageDetailFragment imageDetailFragment();
+
+    @ContributesAndroidInjector
+    abstract EtmBackgroundJobsFragment etmBackgroundJobsFragment();
+
+    @ContributesAndroidInjector
+    abstract BackgroundJobManagerImpl backgroundJobManagerImpl();
+
+    @ContributesAndroidInjector
+    abstract TestJob testJob();
 }

+ 40 - 5
app/src/main/java/com/nextcloud/client/etm/pages/EtmBackgroundJobsFragment.kt

@@ -20,6 +20,7 @@
  */
 package com.nextcloud.client.etm.pages
 
+import android.annotation.SuppressLint
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.Menu
@@ -32,15 +33,22 @@ import androidx.lifecycle.Observer
 import androidx.recyclerview.widget.DividerItemDecoration
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
+import com.nextcloud.client.di.Injectable
 import com.nextcloud.client.etm.EtmBaseFragment
+import com.nextcloud.client.jobs.BackgroundJobManagerImpl
 import com.nextcloud.client.jobs.JobInfo
+import com.nextcloud.client.preferences.AppPreferences
 import com.owncloud.android.R
 import java.text.SimpleDateFormat
 import java.util.Locale
+import javax.inject.Inject
 
-class EtmBackgroundJobsFragment : EtmBaseFragment() {
+class EtmBackgroundJobsFragment : EtmBaseFragment(), Injectable {
 
-    class Adapter(private val inflater: LayoutInflater) : RecyclerView.Adapter<Adapter.ViewHolder>() {
+    @Inject
+    lateinit var preferences : AppPreferences
+
+    class Adapter(private val inflater: LayoutInflater, private val preferences: AppPreferences) : RecyclerView.Adapter<Adapter.ViewHolder>(){
 
         class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
             val uuid = view.findViewById<TextView>(R.id.etm_background_job_uuid)
@@ -53,6 +61,7 @@ class EtmBackgroundJobsFragment : EtmBaseFragment() {
             val executionCount = view.findViewById<TextView>(R.id.etm_background_execution_count)
             val executionLog = view.findViewById<TextView>(R.id.etm_background_execution_logs)
             private val executionLogRow = view.findViewById<View>(R.id.etm_background_execution_logs_row)
+            val executionTimesRow = view.findViewById<View>(R.id.etm_background_execution_times_row)
 
             var progressEnabled: Boolean = progressRow.visibility == View.VISIBLE
                 get() {
@@ -93,6 +102,7 @@ class EtmBackgroundJobsFragment : EtmBaseFragment() {
             val view = inflater.inflate(R.layout.etm_background_job_list_item, parent, false)
             val viewHolder = ViewHolder(view)
             viewHolder.logsEnabled = false
+            viewHolder.executionTimesRow.visibility = View.GONE
             view.setOnClickListener {
                 viewHolder.logsEnabled = !viewHolder.logsEnabled
             }
@@ -103,6 +113,7 @@ class EtmBackgroundJobsFragment : EtmBaseFragment() {
             return backgroundJobs.size
         }
 
+        @SuppressLint("SetTextI18n")
         override fun onBindViewHolder(vh: ViewHolder, position: Int) {
             val info = backgroundJobs[position]
             vh.uuid.text = info.id.toString()
@@ -116,8 +127,32 @@ class EtmBackgroundJobsFragment : EtmBaseFragment() {
             } else {
                 vh.progressEnabled = false
             }
-            vh.executionCount.text = "0"
-            vh.executionLog.text = "None"
+
+            val logs =  preferences.readLogEntry()
+            val logsForThisWorker = logs.filter { BackgroundJobManagerImpl.parseTag(it.workerClass)?.second == info.workerClass }
+            if(logsForThisWorker.isNotEmpty()) {
+                vh.executionTimesRow.visibility = View.VISIBLE
+                vh.executionCount.text = logsForThisWorker.filter { it.started != null }.size.toString() + " (${logsForThisWorker.filter { it.finished != null }.size})"
+                var logText = "Worker Logs\n\n" +
+                    "*** Does NOT differentiate between imitate or periodic kinds of Work! ***\n"+
+                    "*** Times run in 48h: Times started (Times finished) ***\n"
+                logsForThisWorker.forEach{
+                    logText += "----------------------\n"
+                    logText += "Worker ${BackgroundJobManagerImpl.parseTag(it.workerClass)?.second}\n"
+                    logText += if (it.started == null){
+                        "ENDED at\n${it.finished}\nWith result: ${it.result}\n"
+                    }else{
+                        "STARTED at\n${it.started}\n"
+                    }
+                }
+                vh.executionLog.text = logText
+            }else{
+                vh.executionLog.text = "Worker Logs\n\n" +
+                    "No Entries -> Maybe logging is not implemented for Worker or it has not run yet."
+                vh.executionCount.text = "0"
+                vh.executionTimesRow.visibility = View.GONE
+            }
+
         }
     }
 
@@ -131,7 +166,7 @@ class EtmBackgroundJobsFragment : EtmBaseFragment() {
 
     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
         val view = inflater.inflate(R.layout.fragment_etm_background_jobs, container, false)
-        adapter = Adapter(inflater)
+        adapter = Adapter(inflater, preferences)
         list = view.findViewById(R.id.etm_background_jobs_list)
         list.layoutManager = LinearLayoutManager(context)
         list.addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))

+ 22 - 10
app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt

@@ -104,6 +104,7 @@ class BackgroundJobFactory @Inject constructor(
                 FilesUploadWorker::class -> createFilesUploadWorker(context, workerParameters)
                 GeneratePdfFromImagesWork::class -> createPDFGenerateWork(context, workerParameters)
                 HealthStatusWork::class -> createHealthStatusWork(context, workerParameters)
+                TestJob::class -> createTestJob(context, workerParameters)
                 else -> null // caller falls back to default factory
             }
         }
@@ -142,7 +143,7 @@ class BackgroundJobFactory @Inject constructor(
             resources,
             arbitraryDataProvider,
             contentResolver,
-            accountManager
+            accountManager,
         )
     }
 
@@ -151,7 +152,7 @@ class BackgroundJobFactory @Inject constructor(
             context,
             params,
             logger,
-            contentResolver
+            contentResolver,
         )
     }
 
@@ -161,7 +162,7 @@ class BackgroundJobFactory @Inject constructor(
             params,
             contentResolver,
             accountManager,
-            preferences
+            preferences,
         )
     }
 
@@ -170,7 +171,7 @@ class BackgroundJobFactory @Inject constructor(
             context,
             params,
             logger,
-            contentResolver
+            contentResolver,
         )
     }
 
@@ -183,7 +184,8 @@ class BackgroundJobFactory @Inject constructor(
             uploadsStorageManager = uploadsStorageManager,
             connectivityService = connectivityService,
             powerManagementService = powerManagementService,
-            syncedFolderProvider = syncedFolderProvider
+            syncedFolderProvider = syncedFolderProvider,
+            backgroundJobManager = backgroundJobManager.get()
         )
     }
 
@@ -208,7 +210,7 @@ class BackgroundJobFactory @Inject constructor(
             preferences,
             clock,
             viewThemeUtils.get(),
-            syncedFolderProvider
+            syncedFolderProvider,
         )
     }
 
@@ -219,7 +221,7 @@ class BackgroundJobFactory @Inject constructor(
             notificationManager,
             accountManager,
             deckApi,
-            viewThemeUtils.get()
+            viewThemeUtils.get(),
         )
     }
 
@@ -245,8 +247,9 @@ class BackgroundJobFactory @Inject constructor(
             accountManager,
             viewThemeUtils.get(),
             localBroadcastManager.get(),
+            backgroundJobManager.get(),
             context,
-            params
+            params,
         )
     }
 
@@ -258,7 +261,7 @@ class BackgroundJobFactory @Inject constructor(
             notificationManager = notificationManager,
             userAccountManager = accountManager,
             logger = logger,
-            params = params
+            params = params,
         )
     }
 
@@ -267,7 +270,16 @@ class BackgroundJobFactory @Inject constructor(
             context,
             params,
             accountManager,
-            arbitraryDataProvider
+            arbitraryDataProvider,
+            backgroundJobManager.get()
+        )
+    }
+
+    private fun createTestJob(context: Context, params: WorkerParameters): TestJob {
+        return TestJob(
+            context,
+            params,
+            backgroundJobManager.get()
         )
     }
 }

+ 5 - 0
app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt

@@ -20,6 +20,7 @@
 package com.nextcloud.client.jobs
 
 import androidx.lifecycle.LiveData
+import androidx.work.ListenableWorker
 import com.nextcloud.client.account.User
 import com.owncloud.android.datamodel.OCFile
 
@@ -35,6 +36,10 @@ interface BackgroundJobManager {
      */
     val jobs: LiveData<List<JobInfo>>
 
+    fun logStartOfWorker(workerName : String?)
+
+    fun logEndOfWorker(workerName: String?, result: ListenableWorker.Result)
+
     /**
      * Start content observer job that monitors changes in media folders
      * and launches synchronization when needed.

+ 34 - 13
app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt

@@ -36,14 +36,13 @@ import androidx.work.WorkManager
 import androidx.work.workDataOf
 import com.nextcloud.client.account.User
 import com.nextcloud.client.core.Clock
+import com.nextcloud.client.di.Injectable
 import com.nextcloud.client.documentscan.GeneratePdfFromImagesWork
 import com.nextcloud.client.preferences.AppPreferences
 import com.owncloud.android.datamodel.OCFile
-import java.time.LocalDate
 import java.util.Date
 import java.util.UUID
 import java.util.concurrent.TimeUnit
-import javax.inject.Inject
 import kotlin.reflect.KClass
 
 /**
@@ -63,11 +62,10 @@ import kotlin.reflect.KClass
 @Suppress("TooManyFunctions") // we expect this implementation to have rich API
 internal class BackgroundJobManagerImpl(
     private val workManager: WorkManager,
-    private val clock: Clock
-) : BackgroundJobManager {
+    private val clock: Clock,
+    private val preferences: AppPreferences
+) : BackgroundJobManager, Injectable {
 
-    @Inject
-    private var preferences: AppPreferences? = null
 
     companion object {
 
@@ -89,6 +87,7 @@ internal class BackgroundJobManagerImpl(
         const val JOB_PDF_GENERATION = "pdf_generation"
         const val JOB_IMMEDIATE_CALENDAR_BACKUP = "immediate_calendar_backup"
         const val JOB_IMMEDIATE_FILES_EXPORT = "immediate_files_export"
+
         const val JOB_PERIODIC_HEALTH_STATUS = "periodic_health_status"
         const val JOB_IMMEDIATE_HEALTH_STATUS = "immediate_health_status"
 
@@ -98,8 +97,9 @@ internal class BackgroundJobManagerImpl(
 
         const val TAG_PREFIX_NAME = "name"
         const val TAG_PREFIX_USER = "user"
+        const val TAG_PREFIX_CLASS = "class"
         const val TAG_PREFIX_START_TIMESTAMP = "timestamp"
-        val PREFIXES = setOf(TAG_PREFIX_NAME, TAG_PREFIX_USER, TAG_PREFIX_START_TIMESTAMP)
+        val PREFIXES = setOf(TAG_PREFIX_NAME, TAG_PREFIX_USER, TAG_PREFIX_START_TIMESTAMP, TAG_PREFIX_CLASS)
         const val NOT_SET_VALUE = "not set"
         const val PERIODIC_BACKUP_INTERVAL_MINUTES = 24 * 60L
         const val DEFAULT_PERIODIC_JOB_INTERVAL_MINUTES = 15L
@@ -116,6 +116,7 @@ internal class BackgroundJobManagerImpl(
         }
 
         fun formatUserTag(user: User): String = "$TAG_PREFIX_USER:${user.accountName}"
+        fun formatClassTag(jobClass: KClass<out ListenableWorker>): String = "$TAG_PREFIX_CLASS:${jobClass.simpleName}"
         fun formatTimeTag(startTimestamp: Long): String = "$TAG_PREFIX_START_TIMESTAMP:$startTimestamp"
 
         fun parseTag(tag: String): Pair<String, String>? {
@@ -153,6 +154,7 @@ internal class BackgroundJobManagerImpl(
                     user = metadata.get(TAG_PREFIX_USER) ?: NOT_SET_VALUE,
                     started = timestamp,
                     progress = info.progress.getInt("progress", -1),
+                    workerClass = metadata.get(TAG_PREFIX_CLASS) ?: NOT_SET_VALUE
                 )
             } else {
                 null
@@ -162,8 +164,11 @@ internal class BackgroundJobManagerImpl(
         fun deleteOldLogs(logEntries: MutableList<LogEntry>) : MutableList<LogEntry>{
 
             logEntries.removeIf {
-                return@removeIf it.started != null &&
-                    Date(Date().time - KEEP_LOG_MILLIS).before(it.started)
+                return@removeIf (it.started != null &&
+                        Date(Date().time - KEEP_LOG_MILLIS).after(it.started)) ||
+                    (it.finished != null &&
+                        Date(Date().time - KEEP_LOG_MILLIS).after(it.finished))
+
             }
             return logEntries
 
@@ -172,13 +177,27 @@ internal class BackgroundJobManagerImpl(
 
     }
 
-    fun logStartOfWorker(workerName : String){
-        if (preferences == null) return;
+    override fun logStartOfWorker(workerName : String?) {
+        val logs = deleteOldLogs(preferences.readLogEntry().toMutableList())
 
-        preferences!!.readLogEntry()
+        if (workerName == null) {
+            logs.add(LogEntry(Date(), null, null, NOT_SET_VALUE))
+        }else{
+            logs.add(LogEntry(Date(), null, null, workerName))
+        }
+        preferences.saveLogEntry(logs)
     }
 
-    fun logEndOfWorker(workerName: String)
+    override fun logEndOfWorker(workerName: String?, result: ListenableWorker.Result){
+
+        val logs = deleteOldLogs(preferences.readLogEntry().toMutableList())
+        if (workerName == null) {
+            logs.add(LogEntry(null,Date(),result.toString(), NOT_SET_VALUE))
+        }else{
+            logs.add(LogEntry(null,Date(),result.toString(),workerName))
+        }
+        preferences.saveLogEntry(logs)
+    }
 
     /**
      * Create [OneTimeWorkRequest.Builder] pre-set with common attributes
@@ -192,6 +211,7 @@ internal class BackgroundJobManagerImpl(
             .addTag(TAG_ALL)
             .addTag(formatNameTag(jobName, user))
             .addTag(formatTimeTag(clock.currentTime))
+            .addTag(formatClassTag(jobClass))
         user?.let { builder.addTag(formatUserTag(it)) }
         return builder
     }
@@ -216,6 +236,7 @@ internal class BackgroundJobManagerImpl(
             .addTag(TAG_ALL)
             .addTag(formatNameTag(jobName, user))
             .addTag(formatTimeTag(clock.currentTime))
+            .addTag(formatClassTag(jobClass))
         user?.let { builder.addTag(formatUserTag(it)) }
         return builder
     }

+ 10 - 1
app/src/main/java/com/nextcloud/client/jobs/ContentObserverWork.kt

@@ -41,12 +41,17 @@ class ContentObserverWork(
 ) : Worker(appContext, params) {
 
     override fun doWork(): Result {
+        backgroundJobManager.logStartOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class))
+
         if (params.triggeredContentUris.size > 0) {
             checkAndStartFileSyncJob()
             backgroundJobManager.startMediaFoldersDetectionJob()
         }
         recheduleSelf()
-        return Result.success()
+
+        val result = Result.success()
+        backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class),result)
+        return result
     }
 
     private fun recheduleSelf() {
@@ -59,4 +64,8 @@ class ContentObserverWork(
             backgroundJobManager.startImmediateFilesSyncJob(true, false)
         }
     }
+
+    companion object {
+        val TAG: String = ContentObserverWork::class.java.simpleName
+    }
 }

+ 10 - 3
app/src/main/java/com/nextcloud/client/jobs/FilesSyncWork.kt

@@ -64,7 +64,8 @@ class FilesSyncWork(
     private val uploadsStorageManager: UploadsStorageManager,
     private val connectivityService: ConnectivityService,
     private val powerManagementService: PowerManagementService,
-    private val syncedFolderProvider: SyncedFolderProvider
+    private val syncedFolderProvider: SyncedFolderProvider,
+    private val backgroundJobManager: BackgroundJobManager
 ) : Worker(context, params) {
 
     companion object {
@@ -74,10 +75,14 @@ class FilesSyncWork(
     }
 
     override fun doWork(): Result {
+        backgroundJobManager.logStartOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class))
+
         val overridePowerSaving = inputData.getBoolean(OVERRIDE_POWER_SAVING, false)
         // If we are in power save mode, better to postpone upload
         if (powerManagementService.isPowerSavingEnabled && !overridePowerSaving) {
-            return Result.success()
+            val result = Result.success()
+            backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class),result)
+            return result
         }
         val resources = context.resources
         val lightVersion = resources.getBoolean(R.bool.syncedFolder_light)
@@ -107,7 +112,9 @@ class FilesSyncWork(
                 )
             }
         }
-        return Result.success()
+        val result = Result.success()
+        backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class),result)
+        return result
     }
 
     @Suppress("LongMethod") // legacy code

+ 10 - 2
app/src/main/java/com/nextcloud/client/jobs/FilesUploadWorker.kt

@@ -69,6 +69,7 @@ class FilesUploadWorker(
     val userAccountManager: UserAccountManager,
     val viewThemeUtils: ViewThemeUtils,
     val localBroadcastManager: LocalBroadcastManager,
+    private val backgroundJobManager: BackgroundJobManager,
     val context: Context,
     params: WorkerParameters
 ) : Worker(context, params), OnDatatransferProgressListener {
@@ -80,10 +81,15 @@ class FilesUploadWorker(
     private val fileUploaderDelegate = FileUploaderDelegate()
 
     override fun doWork(): Result {
+        backgroundJobManager.logStartOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class))
+
         val accountName = inputData.getString(ACCOUNT)
         if (accountName.isNullOrEmpty()) {
             Log_OC.w(TAG, "User was null for file upload worker")
-            return Result.failure() // user account is needed
+
+            val result = Result.failure()
+            backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class),result)
+            return result // user account is needed
         }
 
         /*
@@ -100,7 +106,9 @@ class FilesUploadWorker(
         }
 
         Log_OC.d(TAG, "No more pending uploads for account $accountName, stopping work")
-        return Result.success()
+        val result = Result.success()
+        backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class),result)
+        return result // user account is needed
     }
 
     private fun handlePendingUploads(uploads: List<OCUpload>, accountName: String) {

+ 7 - 2
app/src/main/java/com/nextcloud/client/jobs/HealthStatusWork.kt

@@ -42,9 +42,12 @@ class HealthStatusWork(
     private val context: Context,
     params: WorkerParameters,
     private val userAccountManager: UserAccountManager,
-    private val arbitraryDataProvider: ArbitraryDataProvider
+    private val arbitraryDataProvider: ArbitraryDataProvider,
+    private val backgroundJobManager: BackgroundJobManager
 ) : Worker(context, params) {
     override fun doWork(): Result {
+        backgroundJobManager.logStartOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class))
+
         for (user in userAccountManager.allUsers) {
             // only if security guard is enabled
             if (!CapabilityUtils.getCapability(user, context).securityGuard.isTrue) {
@@ -92,7 +95,9 @@ class HealthStatusWork(
             }
         }
 
-        return Result.success()
+        val result = Result.success()
+        backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class),result)
+        return result
     }
 
     private fun collectSyncConflicts(user: User): Problem? {

+ 2 - 2
app/src/main/java/com/nextcloud/client/jobs/JobInfo.kt

@@ -29,9 +29,9 @@ data class JobInfo(
     val state: String = "",
     val name: String = "",
     val user: String = "",
+    val workerClass: String = "",
     val started: Date = Date(0),
     val progress: Int = 0,
-    val executionLog: MutableList<LogEntry>? = null
 )
 
 
@@ -39,5 +39,5 @@ data class LogEntry (
     val started: Date? = null,
     val finished: Date? = null,
     val result: String? = null,
-    var worker: String = "None"
+    var workerClass: String = BackgroundJobManagerImpl.NOT_SET_VALUE
 )

+ 3 - 2
app/src/main/java/com/nextcloud/client/jobs/JobsModule.kt

@@ -24,6 +24,7 @@ import android.content.ContextWrapper
 import androidx.work.Configuration
 import androidx.work.WorkManager
 import com.nextcloud.client.core.Clock
+import com.nextcloud.client.preferences.AppPreferences
 import dagger.Module
 import dagger.Provides
 import javax.inject.Singleton
@@ -50,7 +51,7 @@ class JobsModule {
 
     @Provides
     @Singleton
-    fun backgroundJobManager(workManager: WorkManager, clock: Clock): BackgroundJobManager {
-        return BackgroundJobManagerImpl(workManager, clock)
+    fun backgroundJobManager(workManager: WorkManager, clock: Clock, preferences: AppPreferences): BackgroundJobManager {
+        return BackgroundJobManagerImpl(workManager, clock, preferences)
     }
 }

+ 8 - 2
app/src/main/java/com/nextcloud/client/jobs/TestJob.kt

@@ -26,7 +26,8 @@ import androidx.work.WorkerParameters
 
 class TestJob(
     appContext: Context,
-    params: WorkerParameters
+    params: WorkerParameters,
+    private val backgroundJobManager: BackgroundJobManager
 ) : Worker(appContext, params) {
 
     companion object {
@@ -36,6 +37,8 @@ class TestJob(
     }
 
     override fun doWork(): Result {
+        backgroundJobManager.logStartOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class))
+
         for (i in 0..MAX_PROGRESS) {
             Thread.sleep(DELAY_MS)
             val progress = Data.Builder()
@@ -43,6 +46,9 @@ class TestJob(
                 .build()
             setProgressAsync(progress)
         }
-        return Result.success()
+
+        val result = Result.success()
+        backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class),result)
+        return result
     }
 }

+ 2 - 0
app/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java

@@ -53,6 +53,7 @@ import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import static com.owncloud.android.ui.fragment.OCFileListFragment.FOLDER_LAYOUT_LIST;
+import static java.util.Collections.emptyList;
 
 /**
  * Implementation of application-wide preferences using {@link SharedPreferences}.
@@ -515,6 +516,7 @@ public final class AppPreferencesImpl implements AppPreferences {
     @Override
     public List<LogEntry> readLogEntry() {
         String json = preferences.getString(LOG_ENTRY, null);
+        if (json == null) return emptyList();
         Gson gson = new Gson();
         Type listType = new TypeToken<List<LogEntry>>() {}.getType();
         return gson.fromJson(json, listType);

+ 19 - 16
app/src/main/res/layout/etm_background_job_list_item.xml

@@ -137,8 +137,10 @@
     </TableRow>
 
     <TableRow
+        android:id="@+id/etm_background_execution_times_row"
         android:layout_width="match_parent"
-        android:layout_height="match_parent">
+        android:layout_height="match_parent"
+        android:visibility="visible">
 
         <TextView
             android:layout_width="wrap_content"
@@ -154,27 +156,28 @@
 
     </TableRow>
 
-    <TableRow
-        android:id="@+id/etm_background_execution_logs_row"
+    <HorizontalScrollView
         android:layout_width="match_parent"
-        android:layout_height="wrap_content">
+        android:layout_height="wrap_content" >
 
-        <TextView
+        <TableRow
+            android:id="@+id/etm_background_execution_logs_row"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_marginEnd="20dp"
-            android:text="@string/etm_background_execution_log" />
+            android:fadeScrollbars="false"
+            android:scrollbars="horizontal"
+            android:scrollHorizontally="true">
 
-        <ScrollView
-            android:layout_width="match_parent"
-            android:layout_height="80dp">
 
-            <TextView
-                android:id="@+id/etm_background_execution_logs"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content" />
-        </ScrollView>
+                <TextView
+                    android:id="@+id/etm_background_execution_logs"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_span="2" />
+
+
+        </TableRow>
+    </HorizontalScrollView>
 
-    </TableRow>
 
 </TableLayout>

+ 1 - 1
app/src/main/res/values/strings.xml

@@ -878,7 +878,7 @@
     <string name="etm_background_job_name">Job name</string>
     <string name="etm_background_job_user">User</string>
     <string name="etm_background_job_state">State</string>
-    <string name="etm_background_job_started">Started</string>
+    <string name="etm_background_job_started">Created</string>
     <string name="etm_background_job_progress">Progress</string>
     <string name="etm_background_execution_count">Times run in 48h</string>
     <string name="etm_background_execution_log">Execution logs</string>