Fix flaky tests in MddJobServiceTest.

- Put tests that calling scheduleIfNeeded() to a background
- Apply JobServiceCallback to check onStart or onStop executed.
- Apply ProcessLifeguardRule to prevent test crash on a background thread.
- Move JobServiceCallback to common test-util.

Test: atest -c com.android.adservices.download.MddJobServiceTest
com.android.adservices.cobalt.CobaltJobServiceTest

Bug: 306676682
Bug: 302757068
Change-Id: I241338a4e369e52aa4c8b46e5e732a571eb12f4c
diff --git a/adservices/tests/test-util/java/com/android/adservices/common/JobServiceCallback.java b/adservices/tests/test-util/java/com/android/adservices/common/JobServiceCallback.java
new file mode 100644
index 0000000..73979c1
--- /dev/null
+++ b/adservices/tests/test-util/java/com/android/adservices/common/JobServiceCallback.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 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
+ *
+ *      http://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.android.adservices.common;
+
+import android.app.job.JobService;
+
+/**
+ * Custom {@link SyncCallback} implementation where used for checking methods in {@link JobService}
+ * is called or executed. This implementation must only used in tests.
+ *
+ * <p>Use a {@link Boolean} type as a place holder for received on success. This {@link Boolean} is
+ * used for checking a method has been called when calling {@link #assertResultReceived()}
+ */
+public final class JobServiceCallback extends SyncCallback<Boolean, Void> {
+
+    /**
+     * Injects a boolean {@code true} as Result. This is used for checking a stub method is called.
+     */
+    public void onJobFinished() {
+        super.injectResult(true);
+    }
+
+    /**
+     * Injects a boolean {@code false} as Result. This is used for checking a stub method is called.
+     */
+    public void onJobStopped() {
+        super.injectResult(false);
+    }
+
+    /** Asserts {@link #onJobFinished} was called. */
+    public void assertJobFinished() throws InterruptedException {
+        assertResultReceived();
+    }
+
+    /** Sets a method as expected result. */
+    public void insertJobScheduledResult(Boolean result) {
+        super.injectResult(result);
+    }
+}
diff --git a/adservices/tests/test-util/java/com/android/adservices/mockito/MockitoExpectations.java b/adservices/tests/test-util/java/com/android/adservices/mockito/MockitoExpectations.java
new file mode 100644
index 0000000..12fbcda
--- /dev/null
+++ b/adservices/tests/test-util/java/com/android/adservices/mockito/MockitoExpectations.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 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
+ *
+ *      http://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.android.adservices.mockito;
+
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.job.JobParameters;
+
+import com.android.adservices.service.Flags;
+import com.android.adservices.spe.AdservicesJobServiceLogger;
+
+import org.mockito.verification.VerificationMode;
+
+/** Provides Mockito expectation for common calls. */
+public final class MockitoExpectations {
+    /**
+     * Verifies {@link AdservicesJobServiceLogger#logExecutionStats(int, long, int, int)} was called
+     * with the expected values, using Mockito's {@link VerificationMode} to set the number of times
+     * (like {@code times(2)}) or {@code times(0)}).
+     */
+    public static void verifyBackgroundJobsLogging(
+            AdservicesJobServiceLogger logger, VerificationMode mode) {
+        verify(logger, mode).persistJobExecutionData(anyInt(), anyLong());
+        verify(logger, mode).logExecutionStats(anyInt(), anyLong(), anyInt(), anyInt());
+    }
+
+    /** Verifies {@link AdservicesJobServiceLogger#recordJobSkipped(int, int)} is called once. */
+    public static void verifyBackgroundJobsSkipLogged(AdservicesJobServiceLogger logger) {
+        verify(logger).recordJobSkipped(anyInt(), anyInt());
+        verify(logger).persistJobExecutionData(anyInt(), anyLong());
+        verify(logger)
+                .logExecutionStats(
+                        anyInt(),
+                        anyLong(),
+                        eq(
+                                AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON),
+                        anyInt());
+    }
+
+    /** Verifies {@link AdservicesJobServiceLogger#recordOnStartJob(int)} is called once. */
+    public static void verifyJobFinishedLogged(AdservicesJobServiceLogger logger) {
+        verify(logger).recordJobFinished(anyInt(), anyBoolean(), anyBoolean());
+        verify(logger).recordOnStartJob(anyInt());
+        verify(logger).persistJobExecutionData(anyInt(), anyLong());
+        verify(logger).logExecutionStats(anyInt(), anyLong(), anyInt(), anyInt());
+    }
+
+    /**
+     * Verifies {@link AdservicesJobServiceLogger#recordOnStopJob(JobParameters, int, boolean)} is
+     * called once.
+     */
+    public static void verifyOnStopJobLogged(AdservicesJobServiceLogger logger) {
+        verify(logger).recordOnStopJob(any(), anyInt(), anyBoolean());
+        verify(logger).logExecutionStats(anyInt(), anyLong(), anyInt(), anyInt());
+    }
+
+    /**
+     * Mocks a call to {@link Flags#getBackgroundJobsLoggingKillSwitch()}, returning overrideValue.
+     */
+    public static void mockBackgroundJobsLoggingKillSwitch(Flags flag, boolean overrideValue) {
+        when(flag.getBackgroundJobsLoggingKillSwitch()).thenReturn(overrideValue);
+    }
+
+    private MockitoExpectations() {
+        throw new UnsupportedOperationException("Provides only static methods");
+    }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/cobalt/CobaltJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/cobalt/CobaltJobServiceTest.java
index 22ddd43..5397949 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/cobalt/CobaltJobServiceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/cobalt/CobaltJobServiceTest.java
@@ -19,7 +19,10 @@
 import static com.android.adservices.cobalt.CobaltConstants.DEFAULT_API_KEY;
 import static com.android.adservices.cobalt.CobaltConstants.DEFAULT_RELEASE_STAGE;
 import static com.android.adservices.mockito.ExtendedMockitoExpectations.mockGetFlags;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON;
+import static com.android.adservices.mockito.MockitoExpectations.verifyBackgroundJobsLogging;
+import static com.android.adservices.mockito.MockitoExpectations.verifyBackgroundJobsSkipLogged;
+import static com.android.adservices.mockito.MockitoExpectations.verifyJobFinishedLogged;
+import static com.android.adservices.mockito.MockitoExpectations.verifyOnStopJobLogged;
 import static com.android.adservices.spe.AdservicesJobInfo.COBALT_LOGGING_JOB;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -32,7 +35,6 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -42,13 +44,14 @@
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
+import android.app.job.JobService;
 import android.content.ComponentName;
 import android.content.Context;
 
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.adservices.common.JobServiceCallback;
 import com.android.adservices.common.ProcessLifeguardRule;
-import com.android.adservices.common.SyncCallback;
 import com.android.adservices.mockito.AdServicesExtendedMockitoRule;
 import com.android.adservices.service.Flags;
 import com.android.adservices.service.FlagsFactory;
@@ -123,8 +126,9 @@
         mockBackgroundJobsLoggingKillSwitch(/* overrideValue= */ true);
 
         onStartJob_featureDisabled();
+
         // Verify logging methods are not invoked.
-        verifyBackgroundJobsLoggingNeverCalled();
+        verifyBackgroundJobsLogging(mLogger, never());
     }
 
     @Test
@@ -135,7 +139,7 @@
         onStartJob_featureEnabled();
 
         // Verify logging methods are not invoked.
-        verifyBackgroundJobsLoggingNeverCalled();
+        verifyBackgroundJobsLogging(mLogger, never());
     }
 
     @Test
@@ -146,7 +150,7 @@
         onStartJob_featureDisabled();
 
         // Verify logging methods are invoked.
-        verifyJobSkipLoggedOnce();
+        verifyBackgroundJobsSkipLogged(mLogger);
     }
 
     @Test
@@ -157,7 +161,7 @@
         onStartJob_featureEnabled();
 
         // Verify logging methods are invoked.
-        verifyJobFinishedLoggedOnce();
+        verifyJobFinishedLogged(mLogger);
     }
 
     @Test
@@ -168,7 +172,7 @@
         onStopJob();
 
         // Verify logging methods are not invoked.
-        verifyBackgroundJobsLoggingNeverCalled();
+        verifyBackgroundJobsLogging(mLogger, never());
     }
 
     @Test
@@ -179,7 +183,7 @@
         onStopJob();
 
         // Verify logging methods invoked.
-        verifyOnStopJobLoggedOnce();
+        verifyOnStopJobLogged(mLogger);
     }
 
     @Test
@@ -187,7 +191,7 @@
         // Feature is Enabled.
         mockCobaltLoggingEnabled(/* overrideValue= */ true);
 
-        JobScheduledCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
+        JobServiceCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
 
         assertJobScheduled(callBack, /* shouldSchedule */ true, /* checkPendingJob */ true);
 
@@ -199,7 +203,7 @@
         // Feature is disabled.
         mockCobaltLoggingEnabled(false);
 
-        JobScheduledCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
+        JobServiceCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
 
         assertJobScheduled(callBack, /* shouldSchedule */ false, /* checkPendingJob */ false);
         verifyNoMoreInteractions(staticMockMarker(CobaltFactory.class));
@@ -211,7 +215,7 @@
         mockCobaltLoggingEnabled(/* overrideValue= */ true);
         mockBackgroundJobsLoggingKillSwitch(/* overrideValue= */ true);
 
-        JobScheduledCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
+        JobServiceCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
 
         assertJobScheduled(callBack, /* shouldSchedule */ true, /* checkPendingJob */ true);
 
@@ -229,7 +233,7 @@
                 .getCobaltLoggingJobPeriodMs();
 
         // The first invocation of scheduleIfNeeded() schedules the job.
-        JobScheduledCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
+        JobServiceCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
 
         assertJobScheduled(callBack, /* shouldSchedule */ true, /* checkPendingJob */ true);
 
@@ -252,7 +256,7 @@
                 .when(mMockFlags)
                 .getCobaltLoggingJobPeriodMs();
 
-        JobScheduledCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
+        JobServiceCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
 
         assertJobScheduled(callBack, /* shouldSchedule */ true, /* checkPendingJob */ true);
 
@@ -280,7 +284,7 @@
                 .getCobaltLoggingJobPeriodMs();
 
         // The first invocation of scheduleIfNeeded() schedules the job.
-        JobScheduledCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
+        JobServiceCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
 
         assertJobScheduled(callBack, /* shouldSchedule */ true, /* checkPendingJob */ true);
 
@@ -302,7 +306,7 @@
         onStartJob_shouldDisableJobTrue();
 
         // Verify logging method is not invoked.
-        verifyBackgroundJobsLoggingNeverCalled();
+        verifyBackgroundJobsLogging(mLogger, never());
     }
 
     @Test
@@ -315,7 +319,7 @@
 
         // Verify no logging has happened even though logging is enabled because this field is not
         // logged
-        verifyBackgroundJobsLoggingNeverCalled();
+        verifyBackgroundJobsLogging(mLogger, never());
     }
 
     // TODO(b/296945680): remove Thread.sleep().
@@ -323,8 +327,8 @@
      * Waits for current running job to finish before mocked {@code Flags} finished mocking.
      *
      * <p>Tests needs to call this at the end of the test if scheduled background job to prevent
-     * READ_DEVICE_CONFIG permission error when scheduled job start running after mocked {@code
-     * flags} finished mocking.
+     * {@code android.permission.READ_DEVICE_CONFIG} permission error when scheduled job start
+     * running after mocked {@code flags} finished mocking.
      */
     public void waitForJobFinished(int timeout) throws InterruptedException {
         Thread.sleep(timeout);
@@ -337,11 +341,11 @@
         doReturn(mMockCobaltPeriodicJob)
                 .when(() -> CobaltFactory.getCobaltPeriodicJob(any(), any()));
 
-        JobFinishedGuard jobFinishedGuard = createJobFinishedGuard();
+        JobServiceCallback callback = createJobFinishedCallback(mSpyCobaltJobService);
 
         mSpyCobaltJobService.onStartJob(mMockJobParameters);
 
-        jobFinishedGuard.assertFinished();
+        callback.assertJobFinished();
 
         // Check that generateAggregatedObservations() is executed.
         verify(() -> CobaltFactory.getCobaltPeriodicJob(any(Context.class), any(Flags.class)));
@@ -366,13 +370,13 @@
         sJobScheduler.schedule(existingJobInfo);
         assertThat(sJobScheduler.getPendingJob(COBALT_LOGGING_JOB_ID)).isNotNull();
 
-        JobFinishedGuard jobFinishedGuard = createJobFinishedGuard();
+        JobServiceCallback callback = createJobFinishedCallback(mSpyCobaltJobService);
 
         // Now verify that when the Job starts, it will be unscheduled.
         assertThat(mSpyCobaltJobService.onStartJob(mMockJobParameters)).isFalse();
         assertThat(sJobScheduler.getPendingJob(COBALT_LOGGING_JOB_ID)).isNull();
 
-        jobFinishedGuard.assertFinished();
+        callback.assertJobFinished();
 
         verify(mSpyCobaltJobService).jobFinished(mMockJobParameters, false);
         verifyNoMoreInteractions(staticMockMarker(CobaltFactory.class));
@@ -382,11 +386,11 @@
         // Feature is enabled.
         mockCobaltLoggingEnabled(/* overrideValue= */ true);
 
-        JobFinishedGuard jobStoppedGuard = createOnStopJobGuard();
+        JobServiceCallback callback = createOnStopJobCallback(mSpyCobaltJobService);
         // Verify nothing throws.
         mSpyCobaltJobService.onStopJob(mMockJobParameters);
 
-        jobStoppedGuard.assertFinished();
+        callback.assertJobFinished();
     }
 
     private void onStartJob_shouldDisableJobTrue() {
@@ -427,64 +431,6 @@
         doReturn(overrideValue).when(mMockFlags).getCobaltLoggingEnabled();
     }
 
-    private void verifyBackgroundJobsLoggingNeverCalled() {
-        verify(mLogger, never()).persistJobExecutionData(anyInt(), anyLong());
-        verify(mLogger, never()).logExecutionStats(anyInt(), anyLong(), anyInt(), anyInt());
-    }
-
-    private void verifyJobSkipLoggedOnce() {
-        verify(mLogger).recordJobSkipped(anyInt(), anyInt());
-        verify(mLogger).persistJobExecutionData(anyInt(), anyLong());
-        verify(mLogger)
-                .logExecutionStats(
-                        anyInt(),
-                        anyLong(),
-                        eq(
-                                AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON),
-                        anyInt());
-    }
-
-    private void verifyJobFinishedLoggedOnce() {
-        verify(mLogger).recordJobFinished(anyInt(), anyBoolean(), anyBoolean());
-        verify(mLogger).recordOnStartJob(anyInt());
-        verify(mLogger).persistJobExecutionData(anyInt(), anyLong());
-        verify(mLogger).logExecutionStats(anyInt(), anyLong(), anyInt(), anyInt());
-    }
-
-    private void verifyOnStopJobLoggedOnce() {
-        verify(mLogger).recordOnStopJob(any(), anyInt(), anyBoolean());
-        verify(mLogger).logExecutionStats(anyInt(), anyLong(), anyInt(), anyInt());
-    }
-
-    private JobFinishedGuard createJobFinishedGuard() {
-        JobFinishedGuard jobFinishedGuard = new JobFinishedGuard();
-
-        doAnswer(
-                        unusedInvocation -> {
-                            jobFinishedGuard.jobFinished();
-                            return null;
-                        })
-                .when(mSpyCobaltJobService)
-                .jobFinished(any(), anyBoolean());
-
-        return jobFinishedGuard;
-    }
-
-    private JobFinishedGuard createOnStopJobGuard() {
-        JobFinishedGuard jobFinishedGuard = new JobFinishedGuard();
-
-        doAnswer(
-                        invocation -> {
-                            invocation.callRealMethod();
-                            jobFinishedGuard.jobFinished();
-                            return null;
-                        })
-                .when(mSpyCobaltJobService)
-                .onStopJob(any());
-
-        return jobFinishedGuard;
-    }
-
     private void mockCobaltLoggingFlags() {
         // Mock static method FlagsFactory.getFlags() to return Mock Flags.
         mockGetFlags(mMockFlags);
@@ -493,40 +439,19 @@
         when(mMockFlags.getCobaltAdservicesApiKeyHex()).thenReturn(DEFAULT_API_KEY);
     }
 
-    /**
-     * Custom {@link SyncCallback} implementation where used for checking a background job is
-     * finished.
-     *
-     * <p>Use a {@link Boolean} type as a place holder for received on success. This {@link Boolean}
-     * is used for checking a method has been called when calling {@link #assertResultReceived()}
-     */
-    private static final class JobFinishedGuard extends SyncCallback<Boolean, Void> {
-        public void jobFinished() {
-            super.injectResult(true);
-        }
+    private JobServiceCallback scheduleJobInBackground(boolean forceSchedule) {
+        JobServiceCallback callback = new JobServiceCallback();
 
-        public void assertFinished() throws InterruptedException {
-            assertResultReceived();
-        }
-    }
-
-    private JobScheduledCallback scheduleJobInBackground(boolean forceSchedule) {
-        JobScheduledCallback callback = new JobScheduledCallback();
         mExecutorService.execute(
                 () ->
                         callback.insertJobScheduledResult(
                                 CobaltJobService.scheduleIfNeeded(sContext, forceSchedule)));
+
         return callback;
     }
 
-    private static final class JobScheduledCallback extends SyncCallback<Boolean, Void> {
-        public void insertJobScheduledResult(Boolean result) {
-            super.injectResult(result);
-        }
-    }
-
     private void assertJobScheduled(
-            JobScheduledCallback callback, boolean shouldSchedule, boolean checkPendingJob)
+            JobServiceCallback callback, boolean shouldSchedule, boolean checkPendingJob)
             throws InterruptedException {
         assertThat(callback.assertResultReceived()).isEqualTo(shouldSchedule);
 
@@ -534,4 +459,33 @@
             assertThat(sJobScheduler.getPendingJob(COBALT_LOGGING_JOB_ID)).isNotNull();
         }
     }
+
+    private JobServiceCallback createJobFinishedCallback(JobService jobService) {
+        JobServiceCallback callback = new JobServiceCallback();
+
+        doAnswer(
+                        unusedInvocation -> {
+                            callback.onJobFinished();
+                            return null;
+                        })
+                .when(jobService)
+                .jobFinished(any(), anyBoolean());
+
+        return callback;
+    }
+
+    private JobServiceCallback createOnStopJobCallback(JobService jobService) {
+        JobServiceCallback callback = new JobServiceCallback();
+
+        doAnswer(
+                        invocation -> {
+                            invocation.callRealMethod();
+                            callback.onJobStopped();
+                            return null;
+                        })
+                .when(jobService)
+                .onStopJob(any());
+
+        return callback;
+    }
 }
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/download/MddJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/download/MddJobServiceTest.java
index 3b8f841..40a5c26 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/download/MddJobServiceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/download/MddJobServiceTest.java
@@ -17,28 +17,35 @@
 package com.android.adservices.download;
 
 import static com.android.adservices.download.MddJobService.KEY_MDD_TASK_TAG;
-import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON;
+import static com.android.adservices.mockito.ExtendedMockitoExpectations.mockGetFlags;
+import static com.android.adservices.mockito.MockitoExpectations.mockBackgroundJobsLoggingKillSwitch;
+import static com.android.adservices.mockito.MockitoExpectations.verifyBackgroundJobsLogging;
+import static com.android.adservices.mockito.MockitoExpectations.verifyBackgroundJobsSkipLogged;
+import static com.android.adservices.mockito.MockitoExpectations.verifyJobFinishedLogged;
+import static com.android.adservices.mockito.MockitoExpectations.verifyOnStopJobLogged;
 import static com.android.adservices.spe.AdservicesJobInfo.MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB;
 import static com.android.adservices.spe.AdservicesJobInfo.MDD_CHARGING_PERIODIC_TASK_JOB;
 import static com.android.adservices.spe.AdservicesJobInfo.MDD_MAINTENANCE_PERIODIC_TASK_JOB;
 import static com.android.adservices.spe.AdservicesJobInfo.MDD_WIFI_CHARGING_PERIODIC_TASK_JOB;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
 import static com.google.android.libraries.mobiledatadownload.TaskScheduler.WIFI_CHARGING_PERIODIC_TASK;
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
@@ -46,40 +53,38 @@
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
+import android.app.job.JobService;
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.PersistableBundle;
 
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.adservices.common.JobServiceCallback;
+import com.android.adservices.common.ProcessLifeguardRule;
+import com.android.adservices.mockito.AdServicesExtendedMockitoRule;
 import com.android.adservices.service.Flags;
 import com.android.adservices.service.FlagsFactory;
 import com.android.adservices.service.common.compat.ServiceCompatUtils;
 import com.android.adservices.service.stats.Clock;
 import com.android.adservices.service.stats.StatsdAdServicesLogger;
 import com.android.adservices.spe.AdservicesJobServiceLogger;
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
 
 import com.google.android.libraries.mobiledatadownload.MobileDataDownload;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.MockitoSession;
 import org.mockito.Spy;
-import org.mockito.quality.Strictness;
 
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 /** Unit tests for {@link com.android.adservices.download.MddJobService} */
 public class MddJobServiceTest {
-    private static final int BACKGROUND_TASK_TIMEOUT_MS = 5_000;
     private static final int JOB_SCHEDULED_WAIT_TIME_MS = 1_000;
-
     private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
     private static final JobScheduler JOB_SCHEDULER = CONTEXT.getSystemService(JobScheduler.class);
     private static final int MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID =
@@ -90,53 +95,53 @@
             MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB.getJobId();
     private static final int MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID =
             MDD_WIFI_CHARGING_PERIODIC_TASK_JOB.getJobId();
+    private static final long TASK_PERIOD = 21_600L;
+    public static final int INTERVAL_MS = 10_000;
+    public static final int FLEX_MS = 1_000;
 
     @Spy private MddJobService mSpyMddJobService;
-    private MockitoSession mStaticMockSession;
 
     @Mock JobParameters mMockJobParameters;
 
     @Mock MobileDataDownload mMockMdd;
-    @Mock MobileDataDownloadFactory mMockMddFactory;
     @Mock Flags mMockFlags;
     @Mock MddFlags mMockMddFlags;
     @Mock StatsdAdServicesLogger mMockStatsdLogger;
+
+    private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
     private AdservicesJobServiceLogger mLogger;
 
+    @Rule(order = 0)
+    public final AdServicesExtendedMockitoRule mAdServicesExtendedMockitoRule =
+            new AdServicesExtendedMockitoRule.Builder(this)
+                    .spyStatic(MddJobService.class)
+                    .spyStatic(MobileDataDownloadFactory.class)
+                    .spyStatic(FlagsFactory.class)
+                    .spyStatic(MddFlags.class)
+                    .spyStatic(AdservicesJobServiceLogger.class)
+                    .mockStatic(ServiceCompatUtils.class)
+                    .build();
+
+    @Rule(order = 1)
+    public final ProcessLifeguardRule processLifeguard = new ProcessLifeguardRule();
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
-
-        // Start a mockitoSession to mock static method.
-        mStaticMockSession =
-                ExtendedMockito.mockitoSession()
-                        .spyStatic(MddJobService.class)
-                        .spyStatic(MobileDataDownloadFactory.class)
-                        .spyStatic(FlagsFactory.class)
-                        .spyStatic(MddFlags.class)
-                        .spyStatic(AdservicesJobServiceLogger.class)
-                        .mockStatic(ServiceCompatUtils.class)
-                        .strictness(Strictness.WARN)
-                        .startMocking();
-
         // Mock JobScheduler invocation in EpochJobService
         assertThat(JOB_SCHEDULER).isNotNull();
         assertNull(
                 "Job already scheduled before setup!",
                 JOB_SCHEDULER.getPendingJob(MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID));
 
-        ExtendedMockito.doReturn(JOB_SCHEDULER)
-                .when(mSpyMddJobService)
-                .getSystemService(JobScheduler.class);
+        mockGetFlags(mMockFlags);
+
+        doReturn(JOB_SCHEDULER).when(mSpyMddJobService).getSystemService(JobScheduler.class);
 
         // Mock AdservicesJobServiceLogger to not actually log the stats to server
         mLogger =
                 spy(new AdservicesJobServiceLogger(CONTEXT, Clock.SYSTEM_CLOCK, mMockStatsdLogger));
-        Mockito.doNothing()
-                .when(mLogger)
-                .logExecutionStats(anyInt(), anyLong(), anyInt(), anyInt());
-        ExtendedMockito.doReturn(mLogger)
-                .when(() -> AdservicesJobServiceLogger.getInstance(any(Context.class)));
+        doNothing().when(mLogger).logExecutionStats(anyInt(), anyLong(), anyInt(), anyInt());
+        doReturn(mLogger).when(() -> AdservicesJobServiceLogger.getInstance(any(Context.class)));
 
         // MDD Task Tag.
         PersistableBundle bundle = new PersistableBundle();
@@ -147,343 +152,338 @@
     @After
     public void teardown() {
         JOB_SCHEDULER.cancelAll();
-        mStaticMockSession.finishMocking();
     }
 
     @Test
-    public void testOnStartJob_killswitchIsOff_withoutLogging() throws InterruptedException {
+    public void testOnStartJob_killswitchIsOff_withoutLogging() throws Exception {
         // Logging killswitch is on.
-        doReturn(true).when(mMockFlags).getBackgroundJobsLoggingKillSwitch();
+        mockBackgroundJobsLoggingKillSwitch(mMockFlags, true);
 
         testOnStartJob_killswitchIsOff();
 
         // Verify logging methods are not invoked.
-        verify(mLogger, never()).persistJobExecutionData(anyInt(), anyLong());
-        verify(mLogger, never()).logExecutionStats(anyInt(), anyLong(), anyInt(), anyInt());
+        verifyBackgroundJobsLogging(mLogger, never());
     }
 
     @Test
-    public void testOnStartJob_killswitchIsOff_withLogging() throws InterruptedException {
+    public void testOnStartJob_killswitchIsOff_withLogging() throws Exception {
         // Logging killswitch is off.
-        doReturn(false).when(mMockFlags).getBackgroundJobsLoggingKillSwitch();
+        mockBackgroundJobsLoggingKillSwitch(mMockFlags, false);
 
         testOnStartJob_killswitchIsOff();
 
         // Verify logging methods are invoked.
-        verify(mLogger).persistJobExecutionData(anyInt(), anyLong());
-        verify(mLogger).logExecutionStats(anyInt(), anyLong(), anyInt(), anyInt());
+        verifyJobFinishedLogged(mLogger);
     }
 
     @Test
-    public void testOnStartJob_killswitchIsOn_withoutLogging() {
+    public void testOnStartJob_killswitchIsOn_withoutLogging() throws Exception {
         // Logging killswitch is on.
-        Mockito.doReturn(true).when(mMockFlags).getBackgroundJobsLoggingKillSwitch();
+        mockBackgroundJobsLoggingKillSwitch(mMockFlags, true);
 
         testOnStartJob_killswitchIsOn();
 
         // Verify logging methods are not invoked.
-        verify(mLogger, never()).persistJobExecutionData(anyInt(), anyLong());
-        verify(mLogger, never()).logExecutionStats(anyInt(), anyLong(), anyInt(), anyInt());
+        verifyBackgroundJobsLogging(mLogger, never());
     }
 
     @Test
-    public void testOnStartJob_killSwitchOn_withLogging() {
+    public void testOnStartJob_killSwitchOn_withLogging() throws Exception {
         // Logging killswitch is off.
-        Mockito.doReturn(false).when(mMockFlags).getBackgroundJobsLoggingKillSwitch();
+        mockBackgroundJobsLoggingKillSwitch(mMockFlags, false);
 
         testOnStartJob_killswitchIsOn();
 
         // Verify logging methods are invoked.
-        verify(mLogger).persistJobExecutionData(anyInt(), anyLong());
-        verify(mLogger)
-                .logExecutionStats(
-                        anyInt(),
-                        anyLong(),
-                        eq(
-                                AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON),
-                        anyInt());
+        verifyBackgroundJobsSkipLogged(mLogger);
     }
 
     @Test
-    public void testSchedule_killswitchOff() throws InterruptedException {
+    public void testSchedule_killswitchOff() throws Exception {
         // Mock static method MddFlags.getInstance() to return Mock MddFlags.
-        ExtendedMockito.doReturn(mMockMddFlags).when(MddFlags::getInstance);
-        // Mock static method FlagsFactory.getFlags() to return Mock Flags.
-        ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
+        mockGetMddFlags();
         // Killswitch is off.
-        doReturn(false).when(mMockFlags).getMddBackgroundTaskKillSwitch();
+        mockBackgroundJobsLoggingKillSwitch(mMockFlags, false);
 
-        assertThat(MddJobService.scheduleIfNeeded(CONTEXT, /* forceSchedule */ false)).isTrue();
-        // We schedule the job in this test and cancel it in the teardown. There could be a race
-        // condition between when the job starts and when we try to cancel it. If the job already
-        // started and we exited this test method, the flag would not be mocked anymore and hence
-        // the READ_DEVICE_CONFIG exception.
-        // We wait here so that the scheduled job can finish.
-        Thread.sleep(JOB_SCHEDULED_WAIT_TIME_MS);
+        JobServiceCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
+        assertJobScheduled(
+                callBack,
+                MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
+
+        waitForJobFinished(JOB_SCHEDULED_WAIT_TIME_MS);
     }
 
     @Test
-    public void testSchedule_killswitchOn() throws InterruptedException {
-        // Mock static method FlagsFactory.getFlags() to return Mock Flags.
-        ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
+    public void testSchedule_killswitchOn() throws Exception {
         // Killswitch is off.
-        doReturn(true).when(mMockFlags).getMddBackgroundTaskKillSwitch();
+        mockMddBackgroundTaskKillSwitch(/* toBeReturned */ true);
 
-        mSpyMddJobService.scheduleIfNeeded(CONTEXT, false);
+        JobServiceCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
 
         verifyZeroInteractions(staticMockMarker(MobileDataDownloadFactory.class));
-        assertThat(MddJobService.scheduleIfNeeded(CONTEXT, /* forceSchedule */ false)).isFalse();
-        // We schedule the job in this test and cancel it in the teardown. There could be a race
-        // condition between when the job starts and when we try to cancel it. If the job already
-        // started and we exited this test method, the flag would not be mocked anymore and hence
-        // the READ_DEVICE_CONFIG exception.
-        // We wait here so that the scheduled job can finish.
-        Thread.sleep(JOB_SCHEDULED_WAIT_TIME_MS);
+        assertJobScheduled(
+                callBack,
+                MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ false,
+                /* checkPendingJob */ false);
+
+        waitForJobFinished(JOB_SCHEDULED_WAIT_TIME_MS);
     }
 
     @Test
-    public void testOnStopJob_withoutLogging() {
-        // Mock static method FlagsFactory.getFlags() to return Mock Flags.
-        ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
+    public void testOnStopJob_withoutLogging() throws Exception {
         // Logging killswitch is on.
-        doReturn(true).when(mMockFlags).getBackgroundJobsLoggingKillSwitch();
+        mockBackgroundJobsLoggingKillSwitch(mMockFlags, true);
 
         testOnStopJob();
 
         // Verify logging methods are not invoked.
-        verify(mLogger, never()).persistJobExecutionData(anyInt(), anyLong());
-        verify(mLogger, never()).logExecutionStats(anyInt(), anyLong(), anyInt(), anyInt());
+        verifyBackgroundJobsLogging(mLogger, never());
     }
 
     @Test
-    public void testOnStopJob_withLogging() {
-        // Mock static method FlagsFactory.getFlags() to return Mock Flags.
-        ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
+    public void testOnStopJob_withLogging() throws Exception {
         // Logging killswitch is off.
-        doReturn(false).when(mMockFlags).getBackgroundJobsLoggingKillSwitch();
+        mockBackgroundJobsLoggingKillSwitch(mMockFlags, false);
 
         testOnStopJob();
 
         // Verify logging methods are invoked.
-        verify(mLogger).logExecutionStats(anyInt(), anyLong(), anyInt(), anyInt());
+        verifyOnStopJobLogged(mLogger);
     }
 
     @Test
-    public void testScheduleIfNeeded_Success() throws InterruptedException {
+    public void testScheduleIfNeeded_Success() throws Exception {
         // Mock static method MddFlags.getInstance() to return Mock MddFlags.
-        ExtendedMockito.doReturn(mMockMddFlags).when(MddFlags::getInstance);
-        // Mock static method FlagsFactory.getFlags() to return Mock Flags.
-        ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
+        mockGetMddFlags();
 
-        assertThat(MddJobService.scheduleIfNeeded(CONTEXT, false)).isTrue();
-        assertThat(JOB_SCHEDULER.getPendingJob(MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID)).isNotNull();
-        assertThat(JOB_SCHEDULER.getPendingJob(MDD_CHARGING_PERIODIC_TASK_JOB_ID)).isNotNull();
-        assertThat(JOB_SCHEDULER.getPendingJob(MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB_ID))
-                .isNotNull();
-        assertThat(JOB_SCHEDULER.getPendingJob(MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID)).isNotNull();
-        // We schedule the job in this test and cancel it in the teardown. There could be a race
-        // condition between when the job starts and when we try to cancel it. If the job already
-        // started and we exited this test method, the flag would not be mocked anymore and hence
-        // the READ_DEVICE_CONFIG exception.
-        // We wait here so that the scheduled job can finish.
-        Thread.sleep(JOB_SCHEDULED_WAIT_TIME_MS);
+        JobServiceCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
+        assertJobScheduled(
+                callBack,
+                MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
+        assertJobScheduled(
+                callBack,
+                MDD_CHARGING_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
+        assertJobScheduled(
+                callBack,
+                MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
+        assertJobScheduled(
+                callBack,
+                MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
+
+        waitForJobFinished(JOB_SCHEDULED_WAIT_TIME_MS);
     }
 
     @Test
-    public void testScheduleIfNeeded_ScheduledWithSameParameters() throws InterruptedException {
+    public void testScheduleIfNeeded_ScheduledWithSameParameters() throws Exception {
         // Mock static method MddFlags.getInstance() to return Mock MddFlags.
-        ExtendedMockito.doReturn(mMockMddFlags).when(MddFlags::getInstance);
-        // Mock static method FlagsFactory.getFlags() to return Mock Flags.
-        ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
-        doReturn(/* MaintenanceGcmTaskPeriod default value */ 86400L)
-                .when(mMockMddFlags)
-                .maintenanceGcmTaskPeriod();
+        mockGetMddFlags();
+        when(mMockMddFlags.maintenanceGcmTaskPeriod()).thenReturn(TASK_PERIOD);
 
         // The first invocation of scheduleIfNeeded() schedules the job.
-        assertThat(MddJobService.scheduleIfNeeded(CONTEXT, /* forceSchedule */ false)).isTrue();
-        assertThat(JOB_SCHEDULER.getPendingJob(MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID)).isNotNull();
+        JobServiceCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
+        assertJobScheduled(
+                callBack,
+                MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
 
         // The second invocation of scheduleIfNeeded() with same parameters skips the scheduling.
-        assertThat(MddJobService.scheduleIfNeeded(CONTEXT, /* forceSchedule */ false)).isFalse();
-        // We schedule the job in this test and cancel it in the teardown. There could be a race
-        // condition between when the job starts and when we try to cancel it. If the job already
-        // started and we exited this test method, the flag would not be mocked anymore and hence
-        // the READ_DEVICE_CONFIG exception.
-        // We wait here so that the scheduled job can finish.
-        Thread.sleep(JOB_SCHEDULED_WAIT_TIME_MS);
+        callBack = scheduleJobInBackground(/* forceSchedule */ false);
+        assertJobScheduled(
+                callBack,
+                MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ false,
+                /* checkPendingJob */ false);
+
+        waitForJobFinished(JOB_SCHEDULED_WAIT_TIME_MS);
     }
 
     @Test
-    public void testScheduleIfNeeded_ScheduledWithDifferentParameters()
-            throws InterruptedException {
+    public void testScheduleIfNeeded_ScheduledWithDifferentParameters() throws Exception {
         // Mock static method MddFlags.getInstance() to return Mock MddFlags.
-        ExtendedMockito.doReturn(mMockMddFlags).when(MddFlags::getInstance);
-        // Mock static method FlagsFactory.getFlags() to return Mock Flags.
-        ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
-        doReturn(/* MaintenanceGcmTaskPeriod default value */ 86400L)
-                .when(mMockMddFlags)
-                .maintenanceGcmTaskPeriod();
+        mockGetMddFlags();
+        when(mMockMddFlags.maintenanceGcmTaskPeriod()).thenReturn(TASK_PERIOD);
 
         // The first invocation of scheduleIfNeeded() schedules the job.
-        assertThat(MddJobService.scheduleIfNeeded(CONTEXT, /* forceSchedule */ false)).isTrue();
-        assertThat(JOB_SCHEDULER.getPendingJob(MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID)).isNotNull();
+        JobServiceCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
+        assertJobScheduled(
+                callBack,
+                MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
 
-        doReturn(/* MaintenanceGcmTaskPeriod default value */ 86400L + 1L)
-                .when(mMockMddFlags)
-                .maintenanceGcmTaskPeriod();
-        // The second invocation of scheduleIfNeeded() with same parameters skips the scheduling.
-        assertThat(MddJobService.scheduleIfNeeded(CONTEXT, /* forceSchedule */ false)).isTrue();
-        // We schedule the job in this test and cancel it in the teardown. There could be a race
-        // condition between when the job starts and when we try to cancel it. If the job already
-        // started and we exited this test method, the flag would not be mocked anymore and hence
-        // the READ_DEVICE_CONFIG exception.
-        // We wait here so that the scheduled job can finish.
-        Thread.sleep(JOB_SCHEDULED_WAIT_TIME_MS);
+        when(mMockMddFlags.maintenanceGcmTaskPeriod()).thenReturn(TASK_PERIOD + 1L);
+        // The second invocation of scheduleIfNeeded() with different parameters should schedule a
+        // new job.
+        callBack = scheduleJobInBackground(/* forceSchedule */ false);
+        assertJobScheduled(
+                callBack,
+                MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
+
+        waitForJobFinished(JOB_SCHEDULED_WAIT_TIME_MS);
     }
 
     @Test
-    public void testScheduleIfNeeded_forceRun() throws InterruptedException {
+    public void testScheduleIfNeeded_forceRun() throws Exception {
         // Mock static method MddFlags.getInstance() to return Mock MddFlags.
-        ExtendedMockito.doReturn(mMockMddFlags).when(MddFlags::getInstance);
-        // Mock static method FlagsFactory.getFlags() to return test Flags.
-        ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
+        mockGetMddFlags();
 
-        doReturn(/* MaintenanceGcmTaskPeriod default value */ 86400L)
-                .when(mMockMddFlags)
-                .maintenanceGcmTaskPeriod();
+        when(mMockMddFlags.maintenanceGcmTaskPeriod()).thenReturn(TASK_PERIOD);
         // The first invocation of scheduleIfNeeded() schedules the job.
-        assertThat(MddJobService.scheduleIfNeeded(CONTEXT, /* forceSchedule */ false)).isTrue();
-        assertThat(JOB_SCHEDULER.getPendingJob(MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID)).isNotNull();
-        assertThat(JOB_SCHEDULER.getPendingJob(MDD_CHARGING_PERIODIC_TASK_JOB_ID)).isNotNull();
-        assertThat(JOB_SCHEDULER.getPendingJob(MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB_ID))
-                .isNotNull();
-        assertThat(JOB_SCHEDULER.getPendingJob(MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID)).isNotNull();
+        JobServiceCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
+        assertJobScheduled(
+                callBack,
+                MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
+        assertJobScheduled(
+                callBack,
+                MDD_CHARGING_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
+        assertJobScheduled(
+                callBack,
+                MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
+        assertJobScheduled(
+                callBack,
+                MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
 
         // The second invocation of scheduleIfNeeded() with same parameters skips the scheduling.
-        assertThat(MddJobService.scheduleIfNeeded(CONTEXT, /* forceSchedule */ false)).isFalse();
+        callBack = scheduleJobInBackground(/* forceSchedule */ false);
+        assertJobScheduled(
+                callBack,
+                MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ false,
+                /* checkPendingJob */ false);
 
         // The third invocation of scheduleIfNeeded() is forced and re-schedules the job.
-        assertThat(MddJobService.scheduleIfNeeded(CONTEXT, /* forceSchedule */ true)).isTrue();
-        // We schedule the job in this test and cancel it in the teardown. There could be a race
-        // condition between when the job starts and when we try to cancel it. If the job already
-        // started and we exited this test method, the flag would not be mocked anymore and hence
-        // the READ_DEVICE_CONFIG exception.
-        // We wait here so that the scheduled job can finish.
-        Thread.sleep(JOB_SCHEDULED_WAIT_TIME_MS);
+        callBack = scheduleJobInBackground(/* forceSchedule */ true);
+        assertJobScheduled(
+                callBack,
+                MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
+        assertJobScheduled(
+                callBack,
+                MDD_CHARGING_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
+        assertJobScheduled(
+                callBack,
+                MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
+        assertJobScheduled(
+                callBack,
+                MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
+
+        waitForJobFinished(JOB_SCHEDULED_WAIT_TIME_MS);
     }
 
     @Test
-    public void testScheduleIfNeededMddSingleTask_mddMaintenancePeriodicTask()
-            throws InterruptedException {
+    public void testScheduleIfNeededMddSingleTask_mddMaintenancePeriodicTask() throws Exception {
         // Mock static method MddFlags.getInstance() to return Mock MddFlags.
-        ExtendedMockito.doReturn(mMockMddFlags).when(MddFlags::getInstance);
-        // Mock static method FlagsFactory.getFlags() to return test Flags.
-        ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
-        doReturn(/* MaintenanceGcmTaskPeriod default value */ 86400L)
-                .when(mMockMddFlags)
-                .maintenanceGcmTaskPeriod();
-        assertThat(MddJobService.scheduleIfNeeded(CONTEXT, /* forceSchedule */ false)).isTrue();
-        assertThat(JOB_SCHEDULER.getPendingJob(MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID)).isNotNull();
-        // We schedule the job in this test and cancel it in the teardown. There could be a race
-        // condition between when the job starts and when we try to cancel it. If the job already
-        // started and we exited this test method, the flag would not be mocked anymore and hence
-        // the READ_DEVICE_CONFIG exception.
-        // We wait here so that the scheduled job can finish.
-        Thread.sleep(JOB_SCHEDULED_WAIT_TIME_MS);
+        mockGetMddFlags();
+        when(mMockMddFlags.maintenanceGcmTaskPeriod()).thenReturn(TASK_PERIOD);
+        JobServiceCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
+        assertJobScheduled(
+                callBack,
+                MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
+
+        waitForJobFinished(JOB_SCHEDULED_WAIT_TIME_MS);
     }
 
     @Test
-    public void testScheduleIfNeededMddSingleTask_mddChargingPeriodicTask()
-            throws InterruptedException {
+    public void testScheduleIfNeededMddSingleTask_mddChargingPeriodicTask() throws Exception {
         // Mock static method MddFlags.getInstance() to return Mock MddFlags.
-        ExtendedMockito.doReturn(mMockMddFlags).when(MddFlags::getInstance);
-        // Mock static method FlagsFactory.getFlags() to return test Flags.
-        ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
-        doReturn(/* MaintenanceGcmTaskPeriod default value */ 21600L)
-                .when(mMockMddFlags)
-                .chargingGcmTaskPeriod();
-        assertThat(MddJobService.scheduleIfNeeded(CONTEXT, /* forceSchedule */ false)).isTrue();
-        assertThat(JOB_SCHEDULER.getPendingJob(MDD_CHARGING_PERIODIC_TASK_JOB_ID)).isNotNull();
-        // We schedule the job in this test and cancel it in the teardown. There could be a race
-        // condition between when the job starts and when we try to cancel it. If the job already
-        // started and we exited this test method, the flag would not be mocked anymore and hence
-        // the READ_DEVICE_CONFIG exception.
-        // We wait here so that the scheduled job can finish.
-        Thread.sleep(JOB_SCHEDULED_WAIT_TIME_MS);
+        mockGetMddFlags();
+        when(mMockMddFlags.chargingGcmTaskPeriod()).thenReturn(TASK_PERIOD);
+        JobServiceCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
+        assertJobScheduled(
+                callBack,
+                MDD_CHARGING_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
+
+        waitForJobFinished(JOB_SCHEDULED_WAIT_TIME_MS);
     }
 
     @Test
     public void testScheduleIfNeededMddSingleTask_mddCellularChargingPeriodicTask()
-            throws InterruptedException {
+            throws Exception {
         // Mock static method MddFlags.getInstance() to return Mock MddFlags.
-        ExtendedMockito.doReturn(mMockMddFlags).when(MddFlags::getInstance);
-        // Mock static method FlagsFactory.getFlags() to return test Flags.
-        ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
-        doReturn(/* MaintenanceGcmTaskPeriod default value */ 21600L)
-                .when(mMockMddFlags)
-                .cellularChargingGcmTaskPeriod();
-        assertThat(MddJobService.scheduleIfNeeded(CONTEXT, /* forceSchedule */ false)).isTrue();
-        assertThat(JOB_SCHEDULER.getPendingJob(MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB_ID))
-                .isNotNull();
-        // We schedule the job in this test and cancel it in the teardown. There could be a race
-        // condition between when the job starts and when we try to cancel it. If the job already
-        // started and we exited this test method, the flag would not be mocked anymore and hence
-        // the READ_DEVICE_CONFIG exception.
-        // We wait here so that the scheduled job can finish.
-        Thread.sleep(JOB_SCHEDULED_WAIT_TIME_MS);
+        mockGetMddFlags();
+        when(mMockMddFlags.cellularChargingGcmTaskPeriod()).thenReturn(TASK_PERIOD);
+        JobServiceCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
+        assertJobScheduled(
+                callBack,
+                MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
+
+        waitForJobFinished(JOB_SCHEDULED_WAIT_TIME_MS);
     }
 
     @Test
-    public void testScheduleIfNeededMddSingleTask_mddWifiChargingPeriodicTask()
-            throws InterruptedException {
+    public void testScheduleIfNeededMddSingleTask_mddWifiChargingPeriodicTask() throws Exception {
         // Mock static method MddFlags.getInstance() to return Mock MddFlags.
-        ExtendedMockito.doReturn(mMockMddFlags).when(MddFlags::getInstance);
-        // Mock static method FlagsFactory.getFlags() to return test Flags.
-        ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
-        doReturn(/* MaintenanceGcmTaskPeriod default value */ 21600L)
-                .when(mMockMddFlags)
-                .wifiChargingGcmTaskPeriod();
-        assertThat(MddJobService.scheduleIfNeeded(CONTEXT, /* forceSchedule */ false)).isTrue();
-        assertThat(JOB_SCHEDULER.getPendingJob(MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID)).isNotNull();
-        // We schedule the job in this test and cancel it in the teardown. There could be a race
-        // condition between when the job starts and when we try to cancel it. If the job already
-        // started and we exited this test method, the flag would not be mocked anymore and hence
-        // the READ_DEVICE_CONFIG exception.
-        // We wait here so that the scheduled job can finish.
-        Thread.sleep(JOB_SCHEDULED_WAIT_TIME_MS);
+        mockGetMddFlags();
+        when(mMockMddFlags.wifiChargingGcmTaskPeriod()).thenReturn(TASK_PERIOD);
+        JobServiceCallback callBack = scheduleJobInBackground(/* forceSchedule */ false);
+        assertJobScheduled(
+                callBack,
+                MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID,
+                /* shouldSchedule */ true,
+                /* checkPendingJob */ true);
+
+        waitForJobFinished(JOB_SCHEDULED_WAIT_TIME_MS);
     }
 
     @Test
     public void testOnStartJob_shouldDisableJobTrue_withoutLogging() {
-        // Logging killswitch is on.
-        ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
-        doReturn(true).when(mMockFlags).getBackgroundJobsLoggingKillSwitch();
+        mockBackgroundJobsLoggingKillSwitch(mMockFlags, true);
 
         testOnStartJob_shouldDisableJobTrue();
 
         // Verify logging method is not invoked.
-        verify(mLogger, never()).logExecutionStats(anyInt(), anyLong(), anyInt(), anyInt());
+        verifyBackgroundJobsLogging(mLogger, never());
     }
 
     @Test
     public void testOnStartJob_shouldDisableJobTrue_withLoggingEnabled() {
-        // Logging killswitch is off.
-        ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
-        doReturn(false).when(mMockFlags).getBackgroundJobsLoggingKillSwitch();
+        mockBackgroundJobsLoggingKillSwitch(mMockFlags, false);
 
         testOnStartJob_shouldDisableJobTrue();
 
         // Verify no logging has happened even though logging is enabled because this field is not
         // logged
-        verify(mLogger, never()).logExecutionStats(anyInt(), anyLong(), anyInt(), anyInt());
+        verifyBackgroundJobsLogging(mLogger, never());
     }
 
-    private void testOnStartJob_killswitchIsOn() {
-        // Mock static method FlagsFactory.getFlags() to return Mock Flags.
-        ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
+    private void testOnStartJob_killswitchIsOn() throws InterruptedException {
         // Killswitch is on.
-        doReturn(true).when(mMockFlags).getMddBackgroundTaskKillSwitch();
-
+        mockMddBackgroundTaskKillSwitch(/* toBeReturned */ true);
         doNothing().when(mSpyMddJobService).jobFinished(mMockJobParameters, false);
 
         // Schedule the job to assert after starting that the scheduled job has been cancelled
@@ -492,51 +492,52 @@
                                 MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID,
                                 new ComponentName(CONTEXT, MddJobService.class))
                         .setRequiresCharging(true)
-                        .setPeriodic(/* periodMs */ 10000, /* flexMs */ 1000)
+                        .setPeriodic(INTERVAL_MS, FLEX_MS)
                         .build();
         JOB_SCHEDULER.schedule(existingJobInfo);
-        assertNotNull(JOB_SCHEDULER.getPendingJob(MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID));
+        assertThat(JOB_SCHEDULER.getPendingJob(MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID)).isNotNull();
+
+        JobServiceCallback callback = createJobFinishedCallback(mSpyMddJobService);
 
         // Now verify that when the Job starts, it will unschedule itself.
-        assertFalse(mSpyMddJobService.onStartJob(mMockJobParameters));
+        assertThat(mSpyMddJobService.onStartJob(mMockJobParameters)).isFalse();
+        assertThat(JOB_SCHEDULER.getPendingJob(MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID)).isNull();
 
-        assertNull(JOB_SCHEDULER.getPendingJob(MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID));
+        callback.assertJobFinished();
 
         verify(mSpyMddJobService).jobFinished(mMockJobParameters, false);
         verifyNoMoreInteractions(staticMockMarker(MobileDataDownloadFactory.class));
     }
 
     private void testOnStartJob_killswitchIsOff() throws InterruptedException {
-        // Mock static method FlagsFactory.getFlags() to return Mock Flags.
-        ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags);
         // Killswitch is off.
-        doReturn(false).when(mMockFlags).getMddBackgroundTaskKillSwitch();
+        mockMddBackgroundTaskKillSwitch(/* toBeReturned */ false);
 
-        // Add a countDownLatch to ensure background thread gets executed
-        CountDownLatch countDownLatch = new CountDownLatch(1);
-
-        ExtendedMockito.doReturn(mMockMdd)
+        doReturn(mMockMdd)
                 .when(() -> MobileDataDownloadFactory.getMdd(any(Context.class), any(Flags.class)));
 
+        JobServiceCallback callback = createJobFinishedCallback(mSpyMddJobService);
+
         mSpyMddJobService.onStartJob(mMockJobParameters);
 
-        // The countDownLatch doesn't get decreased and waits until timeout.
-        assertThat(countDownLatch.await(BACKGROUND_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS))
-                .isFalse();
+        callback.assertJobFinished();
 
         // Check that Mdd.handleTask is executed.
-        ExtendedMockito.verify(
-                () -> MobileDataDownloadFactory.getMdd(any(Context.class), any(Flags.class)));
+        verify(() -> MobileDataDownloadFactory.getMdd(any(Context.class), any(Flags.class)));
         verify(mMockMdd).handleTask(WIFI_CHARGING_PERIODIC_TASK);
     }
 
-    private void testOnStopJob() {
+    private void testOnStopJob() throws InterruptedException {
+        JobServiceCallback callback = createOnStopJobCallback(mSpyMddJobService);
+
         // Verify nothing throws
         mSpyMddJobService.onStopJob(mMockJobParameters);
+
+        callback.assertJobFinished();
     }
 
     private void testOnStartJob_shouldDisableJobTrue() {
-        ExtendedMockito.doReturn(true)
+        doReturn(true)
                 .when(
                         () ->
                                 ServiceCompatUtils.shouldDisableExtServicesJobOnTPlus(
@@ -550,7 +551,7 @@
                                 MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID,
                                 new ComponentName(CONTEXT, MddJobService.class))
                         .setRequiresCharging(true)
-                        .setPeriodic(/* periodMs */ 10000, /* flexMs */ 1000)
+                        .setPeriodic(INTERVAL_MS, FLEX_MS)
                         .build();
         JOB_SCHEDULER.schedule(existingJobInfo);
         assertNotNull(JOB_SCHEDULER.getPendingJob(MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID));
@@ -563,4 +564,78 @@
         verify(mSpyMddJobService).jobFinished(mMockJobParameters, false);
         verifyNoMoreInteractions(staticMockMarker(MobileDataDownloadFactory.class));
     }
+
+    // TODO(b/296945680): remove Thread.sleep().
+    /**
+     * Waits for current running job to finish before mocked {@code Flags} finished mocking.
+     *
+     * <p>Tests needs to call this at the end of the test if scheduled background job to prevent
+     * {@code android.permission.READ_DEVICE_CONFIG} permission error when scheduled job start
+     * running after mocked {@code flags} finished mocking.
+     */
+    public void waitForJobFinished(int timeOutMs) throws InterruptedException {
+        Thread.sleep(timeOutMs);
+    }
+
+    private void mockGetMddFlags() {
+        doReturn(mMockMddFlags).when(MddFlags::getInstance);
+    }
+
+    private void mockMddBackgroundTaskKillSwitch(boolean toBeReturned) {
+        doReturn(toBeReturned).when(mMockFlags).getMddBackgroundTaskKillSwitch();
+    }
+
+    private JobServiceCallback createJobFinishedCallback(JobService jobService) {
+        JobServiceCallback callback = new JobServiceCallback();
+
+        doAnswer(
+                        unusedInvocation -> {
+                            callback.onJobFinished();
+                            return null;
+                        })
+                .when(jobService)
+                .jobFinished(any(), anyBoolean());
+
+        return callback;
+    }
+
+    private JobServiceCallback createOnStopJobCallback(JobService jobService) {
+        JobServiceCallback callback = new JobServiceCallback();
+
+        doAnswer(
+                        invocation -> {
+                            invocation.callRealMethod();
+                            callback.onJobStopped();
+                            return null;
+                        })
+                .when(jobService)
+                .onStopJob(any());
+
+        return callback;
+    }
+
+    private JobServiceCallback scheduleJobInBackground(boolean forceSchedule) {
+        JobServiceCallback callback = new JobServiceCallback();
+
+        mExecutorService.execute(
+                () ->
+                        callback.insertJobScheduledResult(
+                                MddJobService.scheduleIfNeeded(CONTEXT, forceSchedule)));
+
+        return callback;
+    }
+
+    private void assertJobScheduled(
+            JobServiceCallback callback, int jobId, boolean shouldSchedule, boolean checkPendingJob)
+            throws InterruptedException {
+        assertWithMessage(
+                        "Check callback received result. jobId: %s, shouldSchedule: %s",
+                        jobId, shouldSchedule)
+                .that(callback.assertResultReceived())
+                .isEqualTo(shouldSchedule);
+
+        if (checkPendingJob) {
+            assertThat(JOB_SCHEDULER.getPendingJob(jobId)).isNotNull();
+        }
+    }
 }