mirror of
https://github.com/nextcloud/android.git
synced 2024-11-28 10:18:59 +03:00
Merge pull request #12306 from nextcloud/add-info-for-background-worker
Add info for background worker execution
This commit is contained in:
commit
14a6d655d9
18 changed files with 298 additions and 29 deletions
|
@ -104,7 +104,7 @@ class BackgroundJobManagerTest {
|
||||||
clock = mock()
|
clock = mock()
|
||||||
whenever(clock.currentTime).thenReturn(TIMESTAMP)
|
whenever(clock.currentTime).thenReturn(TIMESTAMP)
|
||||||
whenever(clock.currentDate).thenReturn(Date(TIMESTAMP))
|
whenever(clock.currentDate).thenReturn(Date(TIMESTAMP))
|
||||||
backgroundJobManager = BackgroundJobManagerImpl(workManager, clock)
|
backgroundJobManager = BackgroundJobManagerImpl(workManager, clock, mock())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertHasRequiredTags(tags: Set<String>, jobName: String, user: User? = null) {
|
fun assertHasRequiredTags(tags: Set<String>, jobName: String, user: User? = null) {
|
||||||
|
|
|
@ -25,6 +25,7 @@ import android.Manifest
|
||||||
import androidx.test.rule.GrantPermissionRule
|
import androidx.test.rule.GrantPermissionRule
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.nextcloud.client.core.ClockImpl
|
import com.nextcloud.client.core.ClockImpl
|
||||||
|
import com.nextcloud.client.preferences.AppPreferencesImpl
|
||||||
import com.nextcloud.test.RetryTestRule
|
import com.nextcloud.test.RetryTestRule
|
||||||
import com.owncloud.android.AbstractIT
|
import com.owncloud.android.AbstractIT
|
||||||
import com.owncloud.android.AbstractOnServerIT
|
import com.owncloud.android.AbstractOnServerIT
|
||||||
|
@ -43,7 +44,8 @@ import java.io.FileInputStream
|
||||||
|
|
||||||
class ContactsBackupIT : AbstractOnServerIT() {
|
class ContactsBackupIT : AbstractOnServerIT() {
|
||||||
val workmanager = WorkManager.getInstance(targetContext)
|
val workmanager = WorkManager.getInstance(targetContext)
|
||||||
private val backgroundJobManager = BackgroundJobManagerImpl(workmanager, ClockImpl())
|
val preferences = AppPreferencesImpl.fromContext(targetContext)
|
||||||
|
private val backgroundJobManager = BackgroundJobManagerImpl(workmanager, ClockImpl(), preferences)
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val writeContactsRule = GrantPermissionRule.grant(Manifest.permission.WRITE_CONTACTS)
|
val writeContactsRule = GrantPermissionRule.grant(Manifest.permission.WRITE_CONTACTS)
|
||||||
|
|
|
@ -23,8 +23,11 @@ package com.nextcloud.client.di;
|
||||||
import com.nextcloud.client.documentscan.DocumentScanActivity;
|
import com.nextcloud.client.documentscan.DocumentScanActivity;
|
||||||
import com.nextcloud.client.editimage.EditImageActivity;
|
import com.nextcloud.client.editimage.EditImageActivity;
|
||||||
import com.nextcloud.client.etm.EtmActivity;
|
import com.nextcloud.client.etm.EtmActivity;
|
||||||
|
import com.nextcloud.client.etm.pages.EtmBackgroundJobsFragment;
|
||||||
import com.nextcloud.client.files.downloader.FileTransferService;
|
import com.nextcloud.client.files.downloader.FileTransferService;
|
||||||
|
import com.nextcloud.client.jobs.BackgroundJobManagerImpl;
|
||||||
import com.nextcloud.client.jobs.NotificationWork;
|
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.LogsActivity;
|
||||||
import com.nextcloud.client.logger.ui.LogsViewModel;
|
import com.nextcloud.client.logger.ui.LogsViewModel;
|
||||||
import com.nextcloud.client.media.PlayerService;
|
import com.nextcloud.client.media.PlayerService;
|
||||||
|
@ -478,4 +481,13 @@ abstract class ComponentsModule {
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract ImageDetailFragment imageDetailFragment();
|
abstract ImageDetailFragment imageDetailFragment();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract EtmBackgroundJobsFragment etmBackgroundJobsFragment();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract BackgroundJobManagerImpl backgroundJobManagerImpl();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract TestJob testJob();
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
*/
|
*/
|
||||||
package com.nextcloud.client.etm.pages
|
package com.nextcloud.client.etm.pages
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
|
@ -32,15 +33,23 @@ import androidx.lifecycle.Observer
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.nextcloud.client.di.Injectable
|
||||||
import com.nextcloud.client.etm.EtmBaseFragment
|
import com.nextcloud.client.etm.EtmBaseFragment
|
||||||
|
import com.nextcloud.client.jobs.BackgroundJobManagerImpl
|
||||||
import com.nextcloud.client.jobs.JobInfo
|
import com.nextcloud.client.jobs.JobInfo
|
||||||
|
import com.nextcloud.client.preferences.AppPreferences
|
||||||
import com.owncloud.android.R
|
import com.owncloud.android.R
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
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) {
|
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||||
val uuid = view.findViewById<TextView>(R.id.etm_background_job_uuid)
|
val uuid = view.findViewById<TextView>(R.id.etm_background_job_uuid)
|
||||||
|
@ -50,6 +59,10 @@ class EtmBackgroundJobsFragment : EtmBaseFragment() {
|
||||||
val started = view.findViewById<TextView>(R.id.etm_background_job_started)
|
val started = view.findViewById<TextView>(R.id.etm_background_job_started)
|
||||||
val progress = view.findViewById<TextView>(R.id.etm_background_job_progress)
|
val progress = view.findViewById<TextView>(R.id.etm_background_job_progress)
|
||||||
private val progressRow = view.findViewById<View>(R.id.etm_background_job_progress_row)
|
private val progressRow = view.findViewById<View>(R.id.etm_background_job_progress_row)
|
||||||
|
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
|
var progressEnabled: Boolean = progressRow.visibility == View.VISIBLE
|
||||||
get() {
|
get() {
|
||||||
|
@ -63,6 +76,19 @@ class EtmBackgroundJobsFragment : EtmBaseFragment() {
|
||||||
View.GONE
|
View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var logsEnabled: Boolean = executionLogRow.visibility == View.VISIBLE
|
||||||
|
get() {
|
||||||
|
return executionLogRow.visibility == View.VISIBLE
|
||||||
|
}
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
executionLogRow.visibility = if (value) {
|
||||||
|
View.VISIBLE
|
||||||
|
} else {
|
||||||
|
View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:MM:ssZ", Locale.getDefault())
|
private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:MM:ssZ", Locale.getDefault())
|
||||||
|
@ -74,13 +100,20 @@ class EtmBackgroundJobsFragment : EtmBaseFragment() {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val view = inflater.inflate(R.layout.etm_background_job_list_item, parent, false)
|
val view = inflater.inflate(R.layout.etm_background_job_list_item, parent, false)
|
||||||
return ViewHolder(view)
|
val viewHolder = ViewHolder(view)
|
||||||
|
viewHolder.logsEnabled = false
|
||||||
|
viewHolder.executionTimesRow.visibility = View.GONE
|
||||||
|
view.setOnClickListener {
|
||||||
|
viewHolder.logsEnabled = !viewHolder.logsEnabled
|
||||||
|
}
|
||||||
|
return viewHolder
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
return backgroundJobs.size
|
return backgroundJobs.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onBindViewHolder(vh: ViewHolder, position: Int) {
|
override fun onBindViewHolder(vh: ViewHolder, position: Int) {
|
||||||
val info = backgroundJobs[position]
|
val info = backgroundJobs[position]
|
||||||
vh.uuid.text = info.id.toString()
|
vh.uuid.text = info.id.toString()
|
||||||
|
@ -94,6 +127,34 @@ class EtmBackgroundJobsFragment : EtmBaseFragment() {
|
||||||
} else {
|
} else {
|
||||||
vh.progressEnabled = false
|
vh.progressEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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} " +
|
||||||
|
"(${logsForThisWorker.filter { it.finished != null }.size})"
|
||||||
|
var logText = "Worker Logs\n\n" +
|
||||||
|
"*** Does NOT differentiate between immediate 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +168,7 @@ class EtmBackgroundJobsFragment : EtmBaseFragment() {
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
val view = inflater.inflate(R.layout.fragment_etm_background_jobs, container, false)
|
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 = view.findViewById(R.id.etm_background_jobs_list)
|
||||||
list.layoutManager = LinearLayoutManager(context)
|
list.layoutManager = LinearLayoutManager(context)
|
||||||
list.addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
|
list.addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
|
||||||
|
@ -127,22 +188,27 @@ class EtmBackgroundJobsFragment : EtmBaseFragment() {
|
||||||
vm.cancelAllJobs()
|
vm.cancelAllJobs()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.etm_background_jobs_prune -> {
|
R.id.etm_background_jobs_prune -> {
|
||||||
vm.pruneJobs()
|
vm.pruneJobs()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.etm_background_jobs_start_test -> {
|
R.id.etm_background_jobs_start_test -> {
|
||||||
vm.startTestJob(periodic = false)
|
vm.startTestJob(periodic = false)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.etm_background_jobs_schedule_test -> {
|
R.id.etm_background_jobs_schedule_test -> {
|
||||||
vm.startTestJob(periodic = true)
|
vm.startTestJob(periodic = true)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.etm_background_jobs_cancel_test -> {
|
R.id.etm_background_jobs_cancel_test -> {
|
||||||
vm.cancelTestJob()
|
vm.cancelTestJob()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ import javax.inject.Provider
|
||||||
*
|
*
|
||||||
* This class is doing too many things and should be split up into smaller factories.
|
* This class is doing too many things and should be split up into smaller factories.
|
||||||
*/
|
*/
|
||||||
@Suppress("LongParameterList") // satisfied by DI
|
@Suppress("LongParameterList", "TooManyFunctions") // satisfied by DI
|
||||||
class BackgroundJobFactory @Inject constructor(
|
class BackgroundJobFactory @Inject constructor(
|
||||||
private val logger: Logger,
|
private val logger: Logger,
|
||||||
private val preferences: AppPreferences,
|
private val preferences: AppPreferences,
|
||||||
|
@ -104,6 +104,7 @@ class BackgroundJobFactory @Inject constructor(
|
||||||
FilesUploadWorker::class -> createFilesUploadWorker(context, workerParameters)
|
FilesUploadWorker::class -> createFilesUploadWorker(context, workerParameters)
|
||||||
GeneratePdfFromImagesWork::class -> createPDFGenerateWork(context, workerParameters)
|
GeneratePdfFromImagesWork::class -> createPDFGenerateWork(context, workerParameters)
|
||||||
HealthStatusWork::class -> createHealthStatusWork(context, workerParameters)
|
HealthStatusWork::class -> createHealthStatusWork(context, workerParameters)
|
||||||
|
TestJob::class -> createTestJob(context, workerParameters)
|
||||||
else -> null // caller falls back to default factory
|
else -> null // caller falls back to default factory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,7 +184,8 @@ class BackgroundJobFactory @Inject constructor(
|
||||||
uploadsStorageManager = uploadsStorageManager,
|
uploadsStorageManager = uploadsStorageManager,
|
||||||
connectivityService = connectivityService,
|
connectivityService = connectivityService,
|
||||||
powerManagementService = powerManagementService,
|
powerManagementService = powerManagementService,
|
||||||
syncedFolderProvider = syncedFolderProvider
|
syncedFolderProvider = syncedFolderProvider,
|
||||||
|
backgroundJobManager = backgroundJobManager.get()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,6 +247,7 @@ class BackgroundJobFactory @Inject constructor(
|
||||||
accountManager,
|
accountManager,
|
||||||
viewThemeUtils.get(),
|
viewThemeUtils.get(),
|
||||||
localBroadcastManager.get(),
|
localBroadcastManager.get(),
|
||||||
|
backgroundJobManager.get(),
|
||||||
context,
|
context,
|
||||||
params
|
params
|
||||||
)
|
)
|
||||||
|
@ -267,7 +270,16 @@ class BackgroundJobFactory @Inject constructor(
|
||||||
context,
|
context,
|
||||||
params,
|
params,
|
||||||
accountManager,
|
accountManager,
|
||||||
arbitraryDataProvider
|
arbitraryDataProvider,
|
||||||
|
backgroundJobManager.get()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTestJob(context: Context, params: WorkerParameters): TestJob {
|
||||||
|
return TestJob(
|
||||||
|
context,
|
||||||
|
params,
|
||||||
|
backgroundJobManager.get()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
package com.nextcloud.client.jobs
|
package com.nextcloud.client.jobs
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.work.ListenableWorker
|
||||||
import com.nextcloud.client.account.User
|
import com.nextcloud.client.account.User
|
||||||
import com.owncloud.android.datamodel.OCFile
|
import com.owncloud.android.datamodel.OCFile
|
||||||
|
|
||||||
|
@ -35,6 +36,10 @@ interface BackgroundJobManager {
|
||||||
*/
|
*/
|
||||||
val jobs: LiveData<List<JobInfo>>
|
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
|
* Start content observer job that monitors changes in media folders
|
||||||
* and launches synchronization when needed.
|
* and launches synchronization when needed.
|
||||||
|
|
|
@ -36,7 +36,9 @@ import androidx.work.WorkManager
|
||||||
import androidx.work.workDataOf
|
import androidx.work.workDataOf
|
||||||
import com.nextcloud.client.account.User
|
import com.nextcloud.client.account.User
|
||||||
import com.nextcloud.client.core.Clock
|
import com.nextcloud.client.core.Clock
|
||||||
|
import com.nextcloud.client.di.Injectable
|
||||||
import com.nextcloud.client.documentscan.GeneratePdfFromImagesWork
|
import com.nextcloud.client.documentscan.GeneratePdfFromImagesWork
|
||||||
|
import com.nextcloud.client.preferences.AppPreferences
|
||||||
import com.owncloud.android.datamodel.OCFile
|
import com.owncloud.android.datamodel.OCFile
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
@ -60,10 +62,12 @@ import kotlin.reflect.KClass
|
||||||
@Suppress("TooManyFunctions") // we expect this implementation to have rich API
|
@Suppress("TooManyFunctions") // we expect this implementation to have rich API
|
||||||
internal class BackgroundJobManagerImpl(
|
internal class BackgroundJobManagerImpl(
|
||||||
private val workManager: WorkManager,
|
private val workManager: WorkManager,
|
||||||
private val clock: Clock
|
private val clock: Clock,
|
||||||
) : BackgroundJobManager {
|
private val preferences: AppPreferences
|
||||||
|
) : BackgroundJobManager, Injectable {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val TAG_ALL = "*" // This tag allows us to retrieve list of all jobs run by Nextcloud client
|
const val TAG_ALL = "*" // This tag allows us to retrieve list of all jobs run by Nextcloud client
|
||||||
const val JOB_CONTENT_OBSERVER = "content_observer"
|
const val JOB_CONTENT_OBSERVER = "content_observer"
|
||||||
const val JOB_PERIODIC_CONTACTS_BACKUP = "periodic_contacts_backup"
|
const val JOB_PERIODIC_CONTACTS_BACKUP = "periodic_contacts_backup"
|
||||||
|
@ -82,6 +86,7 @@ internal class BackgroundJobManagerImpl(
|
||||||
const val JOB_PDF_GENERATION = "pdf_generation"
|
const val JOB_PDF_GENERATION = "pdf_generation"
|
||||||
const val JOB_IMMEDIATE_CALENDAR_BACKUP = "immediate_calendar_backup"
|
const val JOB_IMMEDIATE_CALENDAR_BACKUP = "immediate_calendar_backup"
|
||||||
const val JOB_IMMEDIATE_FILES_EXPORT = "immediate_files_export"
|
const val JOB_IMMEDIATE_FILES_EXPORT = "immediate_files_export"
|
||||||
|
|
||||||
const val JOB_PERIODIC_HEALTH_STATUS = "periodic_health_status"
|
const val JOB_PERIODIC_HEALTH_STATUS = "periodic_health_status"
|
||||||
const val JOB_IMMEDIATE_HEALTH_STATUS = "immediate_health_status"
|
const val JOB_IMMEDIATE_HEALTH_STATUS = "immediate_health_status"
|
||||||
|
|
||||||
|
@ -91,13 +96,16 @@ internal class BackgroundJobManagerImpl(
|
||||||
|
|
||||||
const val TAG_PREFIX_NAME = "name"
|
const val TAG_PREFIX_NAME = "name"
|
||||||
const val TAG_PREFIX_USER = "user"
|
const val TAG_PREFIX_USER = "user"
|
||||||
|
const val TAG_PREFIX_CLASS = "class"
|
||||||
const val TAG_PREFIX_START_TIMESTAMP = "timestamp"
|
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 NOT_SET_VALUE = "not set"
|
||||||
const val PERIODIC_BACKUP_INTERVAL_MINUTES = 24 * 60L
|
const val PERIODIC_BACKUP_INTERVAL_MINUTES = 24 * 60L
|
||||||
const val DEFAULT_PERIODIC_JOB_INTERVAL_MINUTES = 15L
|
const val DEFAULT_PERIODIC_JOB_INTERVAL_MINUTES = 15L
|
||||||
const val DEFAULT_IMMEDIATE_JOB_DELAY_SEC = 3L
|
const val DEFAULT_IMMEDIATE_JOB_DELAY_SEC = 3L
|
||||||
|
|
||||||
|
private const val KEEP_LOG_MILLIS = 1000 * 60 * 60 * 24 * 3L
|
||||||
|
|
||||||
fun formatNameTag(name: String, user: User? = null): String {
|
fun formatNameTag(name: String, user: User? = null): String {
|
||||||
return if (user == null) {
|
return if (user == null) {
|
||||||
"$TAG_PREFIX_NAME:$name"
|
"$TAG_PREFIX_NAME:$name"
|
||||||
|
@ -107,6 +115,7 @@ internal class BackgroundJobManagerImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun formatUserTag(user: User): String = "$TAG_PREFIX_USER:${user.accountName}"
|
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 formatTimeTag(startTimestamp: Long): String = "$TAG_PREFIX_START_TIMESTAMP:$startTimestamp"
|
||||||
|
|
||||||
fun parseTag(tag: String): Pair<String, String>? {
|
fun parseTag(tag: String): Pair<String, String>? {
|
||||||
|
@ -120,11 +129,11 @@ internal class BackgroundJobManagerImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseTimestamp(timestamp: String): Date {
|
fun parseTimestamp(timestamp: String): Date {
|
||||||
try {
|
return try {
|
||||||
val ms = timestamp.toLong()
|
val ms = timestamp.toLong()
|
||||||
return Date(ms)
|
Date(ms)
|
||||||
} catch (ex: NumberFormatException) {
|
} catch (ex: NumberFormatException) {
|
||||||
return Date(0)
|
Date(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,12 +152,48 @@ internal class BackgroundJobManagerImpl(
|
||||||
name = metadata.get(TAG_PREFIX_NAME) ?: NOT_SET_VALUE,
|
name = metadata.get(TAG_PREFIX_NAME) ?: NOT_SET_VALUE,
|
||||||
user = metadata.get(TAG_PREFIX_USER) ?: NOT_SET_VALUE,
|
user = metadata.get(TAG_PREFIX_USER) ?: NOT_SET_VALUE,
|
||||||
started = timestamp,
|
started = timestamp,
|
||||||
progress = info.progress.getInt("progress", -1)
|
progress = info.progress.getInt("progress", -1),
|
||||||
|
workerClass = metadata.get(TAG_PREFIX_CLASS) ?: NOT_SET_VALUE
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun deleteOldLogs(logEntries: MutableList<LogEntry>): MutableList<LogEntry> {
|
||||||
|
logEntries.removeIf {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun logStartOfWorker(workerName: String?) {
|
||||||
|
val logs = deleteOldLogs(preferences.readLogEntry().toMutableList())
|
||||||
|
|
||||||
|
if (workerName == null) {
|
||||||
|
logs.add(LogEntry(Date(), null, null, NOT_SET_VALUE))
|
||||||
|
} else {
|
||||||
|
logs.add(LogEntry(Date(), null, null, workerName))
|
||||||
|
}
|
||||||
|
preferences.saveLogEntry(logs)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -163,6 +208,7 @@ internal class BackgroundJobManagerImpl(
|
||||||
.addTag(TAG_ALL)
|
.addTag(TAG_ALL)
|
||||||
.addTag(formatNameTag(jobName, user))
|
.addTag(formatNameTag(jobName, user))
|
||||||
.addTag(formatTimeTag(clock.currentTime))
|
.addTag(formatTimeTag(clock.currentTime))
|
||||||
|
.addTag(formatClassTag(jobClass))
|
||||||
user?.let { builder.addTag(formatUserTag(it)) }
|
user?.let { builder.addTag(formatUserTag(it)) }
|
||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
@ -187,6 +233,7 @@ internal class BackgroundJobManagerImpl(
|
||||||
.addTag(TAG_ALL)
|
.addTag(TAG_ALL)
|
||||||
.addTag(formatNameTag(jobName, user))
|
.addTag(formatNameTag(jobName, user))
|
||||||
.addTag(formatTimeTag(clock.currentTime))
|
.addTag(formatTimeTag(clock.currentTime))
|
||||||
|
.addTag(formatClassTag(jobClass))
|
||||||
user?.let { builder.addTag(formatUserTag(it)) }
|
user?.let { builder.addTag(formatUserTag(it)) }
|
||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,12 +41,17 @@ class ContentObserverWork(
|
||||||
) : Worker(appContext, params) {
|
) : Worker(appContext, params) {
|
||||||
|
|
||||||
override fun doWork(): Result {
|
override fun doWork(): Result {
|
||||||
|
backgroundJobManager.logStartOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class))
|
||||||
|
|
||||||
if (params.triggeredContentUris.size > 0) {
|
if (params.triggeredContentUris.size > 0) {
|
||||||
checkAndStartFileSyncJob()
|
checkAndStartFileSyncJob()
|
||||||
backgroundJobManager.startMediaFoldersDetectionJob()
|
backgroundJobManager.startMediaFoldersDetectionJob()
|
||||||
}
|
}
|
||||||
recheduleSelf()
|
recheduleSelf()
|
||||||
return Result.success()
|
|
||||||
|
val result = Result.success()
|
||||||
|
backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class), result)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun recheduleSelf() {
|
private fun recheduleSelf() {
|
||||||
|
@ -59,4 +64,8 @@ class ContentObserverWork(
|
||||||
backgroundJobManager.startImmediateFilesSyncJob(true, false)
|
backgroundJobManager.startImmediateFilesSyncJob(true, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val TAG: String = ContentObserverWork::class.java.simpleName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,8 @@ class FilesSyncWork(
|
||||||
private val uploadsStorageManager: UploadsStorageManager,
|
private val uploadsStorageManager: UploadsStorageManager,
|
||||||
private val connectivityService: ConnectivityService,
|
private val connectivityService: ConnectivityService,
|
||||||
private val powerManagementService: PowerManagementService,
|
private val powerManagementService: PowerManagementService,
|
||||||
private val syncedFolderProvider: SyncedFolderProvider
|
private val syncedFolderProvider: SyncedFolderProvider,
|
||||||
|
private val backgroundJobManager: BackgroundJobManager
|
||||||
) : Worker(context, params) {
|
) : Worker(context, params) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -74,10 +75,14 @@ class FilesSyncWork(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun doWork(): Result {
|
override fun doWork(): Result {
|
||||||
|
backgroundJobManager.logStartOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class))
|
||||||
|
|
||||||
val overridePowerSaving = inputData.getBoolean(OVERRIDE_POWER_SAVING, false)
|
val overridePowerSaving = inputData.getBoolean(OVERRIDE_POWER_SAVING, false)
|
||||||
// If we are in power save mode, better to postpone upload
|
// If we are in power save mode, better to postpone upload
|
||||||
if (powerManagementService.isPowerSavingEnabled && !overridePowerSaving) {
|
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 resources = context.resources
|
||||||
val lightVersion = resources.getBoolean(R.bool.syncedFolder_light)
|
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
|
@Suppress("LongMethod") // legacy code
|
||||||
|
|
|
@ -69,6 +69,7 @@ class FilesUploadWorker(
|
||||||
val userAccountManager: UserAccountManager,
|
val userAccountManager: UserAccountManager,
|
||||||
val viewThemeUtils: ViewThemeUtils,
|
val viewThemeUtils: ViewThemeUtils,
|
||||||
val localBroadcastManager: LocalBroadcastManager,
|
val localBroadcastManager: LocalBroadcastManager,
|
||||||
|
private val backgroundJobManager: BackgroundJobManager,
|
||||||
val context: Context,
|
val context: Context,
|
||||||
params: WorkerParameters
|
params: WorkerParameters
|
||||||
) : Worker(context, params), OnDatatransferProgressListener {
|
) : Worker(context, params), OnDatatransferProgressListener {
|
||||||
|
@ -80,10 +81,15 @@ class FilesUploadWorker(
|
||||||
private val fileUploaderDelegate = FileUploaderDelegate()
|
private val fileUploaderDelegate = FileUploaderDelegate()
|
||||||
|
|
||||||
override fun doWork(): Result {
|
override fun doWork(): Result {
|
||||||
|
backgroundJobManager.logStartOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class))
|
||||||
|
|
||||||
val accountName = inputData.getString(ACCOUNT)
|
val accountName = inputData.getString(ACCOUNT)
|
||||||
if (accountName.isNullOrEmpty()) {
|
if (accountName.isNullOrEmpty()) {
|
||||||
Log_OC.w(TAG, "User was null for file upload worker")
|
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")
|
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) {
|
private fun handlePendingUploads(uploads: List<OCUpload>, accountName: String) {
|
||||||
|
|
|
@ -42,9 +42,12 @@ class HealthStatusWork(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
params: WorkerParameters,
|
params: WorkerParameters,
|
||||||
private val userAccountManager: UserAccountManager,
|
private val userAccountManager: UserAccountManager,
|
||||||
private val arbitraryDataProvider: ArbitraryDataProvider
|
private val arbitraryDataProvider: ArbitraryDataProvider,
|
||||||
|
private val backgroundJobManager: BackgroundJobManager
|
||||||
) : Worker(context, params) {
|
) : Worker(context, params) {
|
||||||
override fun doWork(): Result {
|
override fun doWork(): Result {
|
||||||
|
backgroundJobManager.logStartOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class))
|
||||||
|
|
||||||
for (user in userAccountManager.allUsers) {
|
for (user in userAccountManager.allUsers) {
|
||||||
// only if security guard is enabled
|
// only if security guard is enabled
|
||||||
if (!CapabilityUtils.getCapability(user, context).securityGuard.isTrue) {
|
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? {
|
private fun collectSyncConflicts(user: User): Problem? {
|
||||||
|
|
|
@ -27,6 +27,14 @@ data class JobInfo(
|
||||||
val state: String = "",
|
val state: String = "",
|
||||||
val name: String = "",
|
val name: String = "",
|
||||||
val user: String = "",
|
val user: String = "",
|
||||||
|
val workerClass: String = "",
|
||||||
val started: Date = Date(0),
|
val started: Date = Date(0),
|
||||||
val progress: Int = 0
|
val progress: Int = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class LogEntry(
|
||||||
|
val started: Date? = null,
|
||||||
|
val finished: Date? = null,
|
||||||
|
val result: String? = null,
|
||||||
|
var workerClass: String = BackgroundJobManagerImpl.NOT_SET_VALUE
|
||||||
|
)
|
||||||
|
|
|
@ -24,6 +24,7 @@ import android.content.ContextWrapper
|
||||||
import androidx.work.Configuration
|
import androidx.work.Configuration
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.nextcloud.client.core.Clock
|
import com.nextcloud.client.core.Clock
|
||||||
|
import com.nextcloud.client.preferences.AppPreferences
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -50,7 +51,11 @@ class JobsModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun backgroundJobManager(workManager: WorkManager, clock: Clock): BackgroundJobManager {
|
fun backgroundJobManager(
|
||||||
return BackgroundJobManagerImpl(workManager, clock)
|
workManager: WorkManager,
|
||||||
|
clock: Clock,
|
||||||
|
preferences: AppPreferences
|
||||||
|
): BackgroundJobManager {
|
||||||
|
return BackgroundJobManagerImpl(workManager, clock, preferences)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,8 @@ import androidx.work.WorkerParameters
|
||||||
|
|
||||||
class TestJob(
|
class TestJob(
|
||||||
appContext: Context,
|
appContext: Context,
|
||||||
params: WorkerParameters
|
params: WorkerParameters,
|
||||||
|
private val backgroundJobManager: BackgroundJobManager
|
||||||
) : Worker(appContext, params) {
|
) : Worker(appContext, params) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -36,6 +37,8 @@ class TestJob(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun doWork(): Result {
|
override fun doWork(): Result {
|
||||||
|
backgroundJobManager.logStartOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class))
|
||||||
|
|
||||||
for (i in 0..MAX_PROGRESS) {
|
for (i in 0..MAX_PROGRESS) {
|
||||||
Thread.sleep(DELAY_MS)
|
Thread.sleep(DELAY_MS)
|
||||||
val progress = Data.Builder()
|
val progress = Data.Builder()
|
||||||
|
@ -43,6 +46,9 @@ class TestJob(
|
||||||
.build()
|
.build()
|
||||||
setProgressAsync(progress)
|
setProgressAsync(progress)
|
||||||
}
|
}
|
||||||
return Result.success()
|
|
||||||
|
val result = Result.success()
|
||||||
|
backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class), result)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,12 @@
|
||||||
package com.nextcloud.client.preferences;
|
package com.nextcloud.client.preferences;
|
||||||
|
|
||||||
import com.nextcloud.appReview.AppReviewShownModel;
|
import com.nextcloud.appReview.AppReviewShownModel;
|
||||||
|
import com.nextcloud.client.jobs.LogEntry;
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
import com.owncloud.android.datamodel.OCFile;
|
||||||
import com.owncloud.android.utils.FileSortOrder;
|
import com.owncloud.android.utils.FileSortOrder;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
@ -317,6 +320,12 @@ public interface AppPreferences {
|
||||||
*/
|
*/
|
||||||
int getLastSeenVersionCode();
|
int getLastSeenVersionCode();
|
||||||
|
|
||||||
|
void saveLogEntry(List<LogEntry> logEntryList);
|
||||||
|
|
||||||
|
List<LogEntry> readLogEntry();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the version code as the last seen version code.
|
* Saves the version code as the last seen version code.
|
||||||
*
|
*
|
||||||
|
|
|
@ -28,11 +28,13 @@ import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
|
|
||||||
|
import com.google.common.reflect.TypeToken;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.nextcloud.appReview.AppReviewShownModel;
|
import com.nextcloud.appReview.AppReviewShownModel;
|
||||||
import com.nextcloud.client.account.User;
|
import com.nextcloud.client.account.User;
|
||||||
import com.nextcloud.client.account.UserAccountManager;
|
import com.nextcloud.client.account.UserAccountManager;
|
||||||
import com.nextcloud.client.account.UserAccountManagerImpl;
|
import com.nextcloud.client.account.UserAccountManagerImpl;
|
||||||
|
import com.nextcloud.client.jobs.LogEntry;
|
||||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
||||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||||
|
@ -41,6 +43,8 @@ import com.owncloud.android.ui.activity.PassCodeActivity;
|
||||||
import com.owncloud.android.ui.activity.SettingsActivity;
|
import com.owncloud.android.ui.activity.SettingsActivity;
|
||||||
import com.owncloud.android.utils.FileSortOrder;
|
import com.owncloud.android.utils.FileSortOrder;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
|
||||||
|
@ -49,6 +53,7 @@ import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import static com.owncloud.android.ui.fragment.OCFileListFragment.FOLDER_LAYOUT_LIST;
|
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}.
|
* Implementation of application-wide preferences using {@link SharedPreferences}.
|
||||||
|
@ -108,6 +113,8 @@ public final class AppPreferencesImpl implements AppPreferences {
|
||||||
private static final String PREF__STORAGE_PERMISSION_REQUESTED = "storage_permission_requested";
|
private static final String PREF__STORAGE_PERMISSION_REQUESTED = "storage_permission_requested";
|
||||||
private static final String PREF__IN_APP_REVIEW_DATA = "in_app_review_data";
|
private static final String PREF__IN_APP_REVIEW_DATA = "in_app_review_data";
|
||||||
|
|
||||||
|
private static final String LOG_ENTRY = "log_entry";
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final SharedPreferences preferences;
|
private final SharedPreferences preferences;
|
||||||
private final UserAccountManager userAccountManager;
|
private final UserAccountManager userAccountManager;
|
||||||
|
@ -499,6 +506,22 @@ public final class AppPreferencesImpl implements AppPreferences {
|
||||||
return preferences.getInt(AUTO_PREF__LAST_SEEN_VERSION_CODE, 0);
|
return preferences.getInt(AUTO_PREF__LAST_SEEN_VERSION_CODE, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveLogEntry(List<LogEntry> logEntryList) {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
String json = gson.toJson(logEntryList);
|
||||||
|
preferences.edit().putString(LOG_ENTRY, json).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setLastSeenVersionCode(int versionCode) {
|
public void setLastSeenVersionCode(int versionCode) {
|
||||||
preferences.edit().putInt(AUTO_PREF__LAST_SEEN_VERSION_CODE, versionCode).apply();
|
preferences.edit().putInt(AUTO_PREF__LAST_SEEN_VERSION_CODE, versionCode).apply();
|
||||||
|
|
|
@ -136,4 +136,48 @@
|
||||||
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow
|
||||||
|
android:id="@+id/etm_background_execution_times_row"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:visibility="visible">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:text="@string/etm_background_execution_count" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/etm_background_execution_count"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="0" />
|
||||||
|
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<HorizontalScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" >
|
||||||
|
|
||||||
|
<TableRow
|
||||||
|
android:id="@+id/etm_background_execution_logs_row"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fadeScrollbars="false"
|
||||||
|
android:scrollbars="horizontal"
|
||||||
|
android:scrollHorizontally="true">
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/etm_background_execution_logs"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_span="2" />
|
||||||
|
|
||||||
|
|
||||||
|
</TableRow>
|
||||||
|
</HorizontalScrollView>
|
||||||
|
|
||||||
|
|
||||||
</TableLayout>
|
</TableLayout>
|
||||||
|
|
|
@ -878,8 +878,9 @@
|
||||||
<string name="etm_background_job_name">Job name</string>
|
<string name="etm_background_job_name">Job name</string>
|
||||||
<string name="etm_background_job_user">User</string>
|
<string name="etm_background_job_user">User</string>
|
||||||
<string name="etm_background_job_state">State</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_job_progress">Progress</string>
|
||||||
|
<string name="etm_background_execution_count">Times run in 48h</string>
|
||||||
<string name="etm_migrations">Migrations (app upgrade)</string>
|
<string name="etm_migrations">Migrations (app upgrade)</string>
|
||||||
<string name="etm_transfer">File transfer</string>
|
<string name="etm_transfer">File transfer</string>
|
||||||
<string name="etm_transfer_remote_path">Remote path</string>
|
<string name="etm_transfer_remote_path">Remote path</string>
|
||||||
|
|
Loading…
Reference in a new issue