Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@
import android.content.Context;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.work.ExistingWorkPolicy;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;

import java.util.concurrent.atomic.AtomicBoolean;

/**
* Helper used to enqueue a one-off background job for initializing ads and
* cookies. The work is only scheduled once per app launch sequence.
*/
public final class StartupInitializer {

private static final String WORK_NAME = "startup_init";
private static final AtomicBoolean HAS_SCHEDULED = new AtomicBoolean(false);

private StartupInitializer() {
// no-op
Expand All @@ -24,10 +28,19 @@ private StartupInitializer() {
* is already enqueued, this call is ignored.
*/
public static void schedule(@NonNull Context context) {
if (!HAS_SCHEDULED.compareAndSet(false, true)) {
return;
}

OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(
StartupWorker.class).build();
WorkManager.getInstance(context).enqueueUniqueWork(
WORK_NAME, ExistingWorkPolicy.KEEP, workRequest);
Comment on lines 30 to 38

Choose a reason for hiding this comment

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

[P1] Guard boolean before enqueue causes permanent skip on failure

The new HAS_SCHEDULED.compareAndSet(false, true) runs before WorkManager.getInstance(context).enqueueUniqueWork(...). If getInstance throws (e.g. WorkManager not yet initialized in this process, a documented possibility) the method exits with the flag stuck at true. Subsequent retries in the same process will return immediately without scheduling the startup work, so initialization never happens. Consider setting the flag only after the enqueue succeeds or resetting it in a catch block to preserve retry behaviour when the first attempt fails.

Useful? React with 👍 / 👎.

}

@VisibleForTesting
static void resetForTesting() {
HAS_SCHEDULED.set(false);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockedStatic;

Expand All @@ -14,9 +16,20 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

public class StartupInitializerTest {

@Before
public void setUp() {
StartupInitializer.resetForTesting();
}

@After
public void tearDown() {
StartupInitializer.resetForTesting();
}

@Test
public void schedule_enqueuesWorkWithKeepPolicy() {
Context context = mock(Context.class);
Expand All @@ -34,4 +47,24 @@ public void schedule_enqueuesWorkWithKeepPolicy() {
);
}
}

@Test
public void schedule_multipleCalls_enqueuesWorkOnlyOnce() {
Context context = mock(Context.class);
WorkManager workManager = mock(WorkManager.class);

try (MockedStatic<WorkManager> mockedWorkManager = mockStatic(WorkManager.class)) {
mockedWorkManager.when(() -> WorkManager.getInstance(context)).thenReturn(workManager);

StartupInitializer.schedule(context);
StartupInitializer.schedule(context);

verify(workManager).enqueueUniqueWork(
eq("startup_init"),
eq(ExistingWorkPolicy.KEEP),
any(OneTimeWorkRequest.class)
);
verifyNoMoreInteractions(workManager);
}
}
}