Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions backgroundwork/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,10 @@ android {
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.appcompat)
implementation(libs.androidx.work.runtime)
implementation(libs.androidx.work.runtime.ktx)
implementation(libs.androidx.work.multiprocess)
implementation(libs.androidx.concurrent.futures.ktx)
implementation(libs.google.android.material)
implementation(libs.guava.android)
}
23 changes: 22 additions & 1 deletion backgroundwork/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,28 @@
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

<application tools:ignore="MissingApplicationIcon" />
<application tools:ignore="MissingApplicationIcon">
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>

<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="location|microphone"
tools:node="merge" />
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.snippets.backgroundwork

import android.content.ComponentName
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkerParameters
import androidx.work.multiprocess.RemoteCoroutineWorker
import androidx.work.multiprocess.RemoteListenableWorker.ARGUMENT_CLASS_NAME
import androidx.work.multiprocess.RemoteListenableWorker.ARGUMENT_PACKAGE_NAME
import androidx.work.multiprocess.RemoteWorkerService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

private object SimpleCoroutineDownloadWorker {
// [START android_background_coroutine_download_worker]
class CoroutineDownloadWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {

override suspend fun doWork(): Result {
val data = downloadSynchronously("https://www.google.com")
saveData(data)
return Result.success()
}
}
// [END android_background_coroutine_download_worker]
}

private object WithContextCoroutineDownloadWorker {
// [START android_background_coroutine_download_worker_with_context]
class CoroutineDownloadWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {

override suspend fun doWork(): Result {
return withContext(Dispatchers.IO) {
val data = downloadSynchronously("https://www.google.com")
saveData(data)
Result.success()
}
}
}
// [END android_background_coroutine_download_worker_with_context]
}

private fun remoteCoroutineWorkerRequest(): OneTimeWorkRequest {
// [START android_background_remote_coroutine_worker_request]
val PACKAGE_NAME = "com.example.background.multiprocess"

val serviceName = RemoteWorkerService::class.java.name
val componentName = ComponentName(PACKAGE_NAME, serviceName)

val data: Data = Data.Builder()
.putString(ARGUMENT_PACKAGE_NAME, componentName.packageName)
.putString(ARGUMENT_CLASS_NAME, componentName.className)
.build()

return OneTimeWorkRequest.Builder(ExampleRemoteCoroutineWorker::class.java)
.setInputData(data)
.build()
// [END android_background_remote_coroutine_worker_request]
}

private fun downloadSynchronously(url: String): String = ""
private fun saveData(data: String) {}

class ExampleRemoteCoroutineWorker(
context: Context,
parameters: WorkerParameters
) : RemoteCoroutineWorker(context, parameters) {
override suspend fun doRemoteWork(): Result = Result.success()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.snippets.backgroundwork

import android.app.Application
import android.content.Context
import androidx.work.Configuration
import androidx.work.WorkManager

// [START android_background_custom_configuration_on_demand]
class MyApplication() : Application(), Configuration.Provider {
override val workManagerConfiguration: Configuration
get() = Configuration.Builder()
.setMinimumLoggingLevel(android.util.Log.INFO)
.build()
}
// [END android_background_custom_configuration_on_demand]

private fun Context.manualInitialization() {
// [START android_background_custom_configuration_manual_init]
// provide custom configuration.
val myConfig = Configuration.Builder()
.setMinimumLoggingLevel(android.util.Log.INFO)
.build()

// initialize WorkManager.
WorkManager.initialize(this, myConfig)
// [END android_background_custom_configuration_manual_init]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/*
* Copyright 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.snippets.backgroundwork

import android.app.Notification
import android.content.Context
import androidx.work.BackoffPolicy
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkRequest
import androidx.work.Worker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import java.util.concurrent.TimeUnit

private fun enqueueWorkSample(myContext: Context) {
// [START android_background_enqueue_work_request]
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>().build()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can simplify lines 40-47 and just fill in the ... even though that would be a visible diff

val myWorkRequest = OneTimeWorkRequestBuilder().build()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping the [START_EXCLUDE silent] D23 Twin (// val myWorkRequest = ...) and parameter binding here to comply with our snippet migration audit rules (D5 Smallest Delta and D23 Same-Line Placeholders), which prohibit inventing an unruled visible RHS when the original DAC snippet used val myWorkRequest = ... (see audit review pass in commit 30aa6be / abf5efb6).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think we should make the code change because line 43 won't be compiled. So I prefer to reduce the amount of code in comments.

@djubinville djubinville Oct 2, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplified android_background_enqueue_work_request in DefineWork.kt to use val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>().build() directly (removing the [START_EXCLUDE silent] block-comment twin) in commit 270d61d1, and documented the visible diff in the PR description.

Skill review note for @hamen (android-snippets-experimental):

  • Rules impacted: D23 (Same-Line Placeholders Ordered 3-Step Test) (SKILL.md lines 150–164) and D5 (Permitted Diff Justifications) (SKILL.md lines 175–179).
  • What needs review in the skill: Under D23, same-line placeholders (= ...) must follow a strict 3-step test:
    1. Use the D23 Twin (/* val x: Type = ... */ inside [START_EXCLUDE silent] followed by a hidden compiling binding) when a type or provenance comment is present.
    2. Bind visibly in the open only if the placeholder matches the closed D23 Literal Table (currently restricted solely to File(...) -> File("photo.jpg")).
    3. Otherwise stop and ask; never invent an unruled visible RHS.
      Here, DRE review prefers replacing val myWorkRequest = ... directly in the visible snippet with val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>().build() to reduce uncompiled code inside /* ... */ comments and [START_EXCLUDE silent] blocks. D23 should be reviewed to clarify when concrete builder/initializer calls (like OneTimeWorkRequestBuilder<MyWork>().build()) are preferred as visible replacements over the /* ... */ D23 Twin pattern.

WorkManager.getInstance(myContext).enqueue(myWorkRequest)
// [END android_background_enqueue_work_request]
}

private fun scheduleOneTimeWorkFrom() {
// [START android_background_schedule_one_time_work_from]
val myWorkRequest = OneTimeWorkRequest.from(MyWork::class.java)
// [END android_background_schedule_one_time_work_from]
}

private fun scheduleOneTimeWorkBuilder() {
// [START android_background_schedule_one_time_work_builder]
val uploadWorkRequest: WorkRequest =
OneTimeWorkRequestBuilder<MyWork>()
// Additional configuration.
.build()
// [END android_background_schedule_one_time_work_builder]
}

private fun expeditedWorkRequest(context: Context) {
// [START android_background_expedited_work_request]
val request = OneTimeWorkRequestBuilder<SyncWorker>()
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()

WorkManager.getInstance(context)
.enqueue(request)
// [END android_background_expedited_work_request]
}

// [START android_background_expedited_coroutine_worker]
class ExpeditedWorker(appContext: Context, workerParams: WorkerParameters) :
CoroutineWorker(appContext, workerParams) {

override suspend fun getForegroundInfo(): ForegroundInfo {
return ForegroundInfo(
NOTIFICATION_ID, createNotification()
)
}

override suspend fun doWork(): Result {
TODO()
}

private fun createNotification(): Notification {
TODO()
}

// [START_EXCLUDE silent]
companion object {
private const val NOTIFICATION_ID = 1
}
// [END_EXCLUDE]
}
// [END android_background_expedited_coroutine_worker]

private fun schedulePeriodicWork() {
// [START android_background_periodic_work_request]
val saveRequest =
PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)
// Additional configuration.
.build()
// [END android_background_periodic_work_request]
}

private fun schedulePeriodicWorkFlex() {
// [START android_background_periodic_work_request_flex]
val myUploadWork = PeriodicWorkRequestBuilder<SaveImageToFileWorker>(
1, TimeUnit.HOURS, // repeatInterval (the period cycle).
15, TimeUnit.MINUTES
) // flexInterval.
.build()
// [END android_background_periodic_work_request_flex]
}

private fun workConstraints() {
// [START android_background_work_constraints]
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.build()

val myWorkRequest: WorkRequest =
OneTimeWorkRequestBuilder<MyWork>()
.setConstraints(constraints)
.build()
// [END android_background_work_constraints]
}

private fun delayedWork() {
// [START android_background_delayed_work]
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
.setInitialDelay(10, TimeUnit.MINUTES)
.build()
// [END android_background_delayed_work]
}

private fun retryBackoffPolicy() {
// [START android_background_retry_backoff_policy]
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
.setBackoffCriteria(
BackoffPolicy.LINEAR,
WorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
)
.build()
// [END android_background_retry_backoff_policy]
}

private fun tagWork() {
// [START android_background_tag_work]
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
.addTag("cleanup")
.build()
// [END android_background_tag_work]
}

// [START android_background_assign_input_data]
// Define the Worker requiring input.
class UploadWork(appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams) {

override fun doWork(): Result {
val imageUriInput =
inputData.getString("IMAGE_URI") ?: return Result.failure()

uploadFile(imageUriInput)
return Result.success()
}
// [START_EXCLUDE]
private fun uploadFile(uri: String) {}
// [END_EXCLUDE]
}

// Create a WorkRequest for your Worker and sending it input.
val myUploadWork = OneTimeWorkRequestBuilder<UploadWork>()
.setInputData(
workDataOf(
"IMAGE_URI" to "http://..."
)
)
.build()
// [END android_background_assign_input_data]

class MyWork(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result = Result.success()
}

class SyncWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result = Result.success()
}

class SaveImageToFileWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result = Result.success()
}
Loading
Loading