UnfinishedWorkListener should not take down the entire process when multiple processes race to initialize and enqueue work in WorkManager.

* Only register the listener in the designated process.

Test: Existing unit tests.
Bug: 333913329
Change-Id: If3121bcf8595fe64b9827839ca21a7db553067ab
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/UnfinishedWorkListener.kt b/work/work-runtime/src/main/java/androidx/work/impl/UnfinishedWorkListener.kt
index 014b025..35720c0 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/UnfinishedWorkListener.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/UnfinishedWorkListener.kt
@@ -17,20 +17,53 @@
 package androidx.work.impl
 
 import android.content.Context
+import androidx.work.Configuration
+import androidx.work.Logger
 import androidx.work.impl.background.systemalarm.RescheduleReceiver
 import androidx.work.impl.utils.PackageManagerHelper
+import androidx.work.impl.utils.isDefaultProcess
+import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.retryWhen
 
-internal fun CoroutineScope.launchUnfinishedWorkListener(appContext: Context, db: WorkDatabase) =
-    db.workSpecDao().hasUnfinishedWorkFlow()
-        .conflate()
-        .distinctUntilChanged()
-        .onEach { hasUnfinishedWork ->
-            PackageManagerHelper.setComponentEnabled(
-                appContext, RescheduleReceiver::class.java, hasUnfinishedWork
-            )
-        }.launchIn(this)
+private val TAG = Logger.tagWithPrefix("UnfinishedWorkListener")
+
+// Backoff policies.
+private const val DELAY_MS = 30_000
+private val MAX_DELAY_MS = TimeUnit.HOURS.toMillis(1)
+
+/**
+ * Keeps track of unfinished work and enables [RescheduleReceiver] when applicable.
+ *
+ * Note: The listener is only ever registered in the designated process. This avoids interference
+ * from other processes given subsequent registrations are redundant.
+ */
+internal fun CoroutineScope.maybeLaunchUnfinishedWorkListener(
+    appContext: Context,
+    configuration: Configuration,
+    db: WorkDatabase
+) {
+    // Only register this in the designated process.
+    if (isDefaultProcess(appContext, configuration)) {
+        db.workSpecDao().hasUnfinishedWorkFlow()
+            .retryWhen { throwable, attempt ->
+                Logger.get().error(TAG, "Cannot check for unfinished work", throwable)
+                // Linear backoff is good enough here.
+                val delayBy = minOf(attempt * DELAY_MS, MAX_DELAY_MS)
+                delay(delayBy)
+                true
+            }
+            .conflate()
+            .distinctUntilChanged()
+            .onEach { hasUnfinishedWork ->
+                PackageManagerHelper.setComponentEnabled(
+                    appContext, RescheduleReceiver::class.java, hasUnfinishedWork
+                )
+            }.launchIn(this)
+    }
+}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
index 27248a9..a8267c4 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -21,7 +21,7 @@
 import static android.text.TextUtils.isEmpty;
 
 import static androidx.work.ListenableFutureKt.executeAsync;
-import static androidx.work.impl.UnfinishedWorkListenerKt.launchUnfinishedWorkListener;
+import static androidx.work.impl.UnfinishedWorkListenerKt.maybeLaunchUnfinishedWorkListener;
 import static androidx.work.impl.WorkManagerImplExtKt.createWorkManager;
 import static androidx.work.impl.WorkManagerImplExtKt.createWorkManagerScope;
 import static androidx.work.impl.WorkerUpdater.enqueueUniquelyNamedPeriodic;
@@ -256,7 +256,7 @@
                 workTaskExecutor.getSerialTaskExecutor(), mWorkDatabase, configuration);
         // Checks for app force stops.
         mWorkTaskExecutor.executeOnTaskThread(new ForceStopRunnable(context, this));
-        launchUnfinishedWorkListener(mWorkManagerScope, mContext, workDatabase);
+        maybeLaunchUnfinishedWorkListener(mWorkManagerScope, mContext, configuration, workDatabase);
     }
 
     /**