chromium/components/background_task_scheduler/internal/android/junit/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskJobServiceTest.java

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.components.background_task_scheduler.internal;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.job.JobParameters;
import android.content.Context;
import android.os.Build;
import android.os.PersistableBundle;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;

import org.chromium.base.ContextUtils;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature;
import org.chromium.components.background_task_scheduler.BackgroundTask;
import org.chromium.components.background_task_scheduler.BackgroundTaskFactory;
import org.chromium.components.background_task_scheduler.TaskIds;
import org.chromium.components.background_task_scheduler.TaskParameters;

import java.util.concurrent.TimeUnit;

/** Unit tests for {@link BackgroundTaskJobService}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = Build.VERSION_CODES.S)
public class BackgroundTaskJobServiceTest {
    private static BackgroundTaskSchedulerJobService.Clock sClock = () -> 1415926535000L;
    private static BackgroundTaskSchedulerJobService.Clock sZeroClock = () -> 0L;
    @Mock private BackgroundTaskSchedulerDelegate mDelegate;
    @Mock private BackgroundTaskSchedulerUma mBackgroundTaskSchedulerUma;
    @Mock private BackgroundTaskSchedulerImpl mBackgroundTaskSchedulerImpl;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        BackgroundTaskSchedulerFactoryInternal.setSchedulerForTesting(
                new BackgroundTaskSchedulerImpl(mDelegate));
        BackgroundTaskSchedulerUma.setInstanceForTesting(mBackgroundTaskSchedulerUma);
        BackgroundTaskSchedulerFactoryInternal.setBackgroundTaskFactory(
                new TestBackgroundTaskFactory());
    }

    @Test
    @Feature({"BackgroundTaskScheduler"})
    public void testOneOffTaskStartsAnytimeWithoutDeadline() {
        JobParameters jobParameters = buildOneOffJobParameters(TaskIds.TEST, null, null);

        BackgroundTaskJobService jobService = new BackgroundTaskJobService();
        assertFalse(jobService.onStartJob(jobParameters));

        verify(mBackgroundTaskSchedulerUma, times(1)).reportTaskStarted(eq(TaskIds.TEST));
    }

    @Test
    @Feature({"BackgroundTaskScheduler"})
    public void testOneOffTaskDoesNotStartExactlyAtDeadline() {
        JobParameters jobParameters =
                buildOneOffJobParameters(TaskIds.TEST, sClock.currentTimeMillis(), Long.valueOf(0));

        BackgroundTaskJobService jobService = new BackgroundTaskJobService();
        jobService.setClockForTesting(sClock);
        assertFalse(jobService.onStartJob(jobParameters));

        verify(mBackgroundTaskSchedulerUma, times(0)).reportTaskStarted(eq(TaskIds.TEST));
    }

    @Test
    @Feature({"BackgroundTaskScheduler"})
    public void testOneOffTaskDoesNotStartAfterDeadline() {
        JobParameters jobParameters =
                buildOneOffJobParameters(
                        TaskIds.TEST, sZeroClock.currentTimeMillis(), Long.valueOf(0));

        BackgroundTaskJobService jobService = new BackgroundTaskJobService();
        jobService.setClockForTesting(sClock);
        assertFalse(jobService.onStartJob(jobParameters));

        verify(mBackgroundTaskSchedulerUma, times(0)).reportTaskStarted(eq(TaskIds.TEST));
    }

    @Test
    @Feature({"BackgroundTaskScheduler"})
    public void testOneOffTaskStartsBeforeDeadline() {
        JobParameters jobParameters =
                buildOneOffJobParameters(
                        TaskIds.TEST, sClock.currentTimeMillis(), sClock.currentTimeMillis());

        BackgroundTaskJobService jobService = new BackgroundTaskJobService();
        jobService.setClockForTesting(sZeroClock);
        assertFalse(jobService.onStartJob(jobParameters));

        verify(mBackgroundTaskSchedulerUma, times(1)).reportTaskStarted(eq(TaskIds.TEST));
    }

    @Test
    @Feature({"BackgroundTaskScheduler"})
    public void testCancelOneOffTaskIfTaskIdNotFound() {
        BackgroundTaskSchedulerFactoryInternal.setSchedulerForTesting(mBackgroundTaskSchedulerImpl);

        JobParameters jobParameters =
                buildOneOffJobParameters(TaskIds.OFFLINE_PAGES_BACKGROUND_JOB_ID, null, null);

        BackgroundTaskJobService jobService = new BackgroundTaskJobService();
        jobService.setClockForTesting(sZeroClock);
        assertFalse(jobService.onStartJob(jobParameters));

        verify(mBackgroundTaskSchedulerImpl, times(1))
                .cancel(
                        eq(ContextUtils.getApplicationContext()),
                        eq(TaskIds.OFFLINE_PAGES_BACKGROUND_JOB_ID));
    }

    @Test
    @Feature({"BackgroundTaskScheduler"})
    public void testPeriodicTaskStartsAnytimeWithoutDeadline() {
        JobParameters jobParameters = buildPeriodicJobParameters(TaskIds.TEST, null, null, null);

        BackgroundTaskJobService jobService = new BackgroundTaskJobService();
        assertFalse(jobService.onStartJob(jobParameters));

        verify(mBackgroundTaskSchedulerUma, times(1)).reportTaskStarted(eq(TaskIds.TEST));
    }

    @Test
    @Feature({"BackgroundTaskScheduler"})
    public void testPeriodicTaskStartsWithinDeadlineTimeFrame() {
        JobParameters jobParameters =
                buildPeriodicJobParameters(
                        TaskIds.TEST,
                        sClock.currentTimeMillis() - TimeUnit.MINUTES.toMillis(13),
                        TimeUnit.MINUTES.toMillis(15),
                        null);

        BackgroundTaskJobService jobService = new BackgroundTaskJobService();
        jobService.setClockForTesting(sClock);
        assertFalse(jobService.onStartJob(jobParameters));

        verify(mBackgroundTaskSchedulerUma, times(1)).reportTaskStarted(eq(TaskIds.TEST));
    }

    @Test
    @Feature({"BackgroundTaskScheduler"})
    public void testPeriodicTaskDoesNotStartExactlyAtDeadline() {
        JobParameters jobParameters =
                buildPeriodicJobParameters(
                        TaskIds.TEST,
                        sClock.currentTimeMillis(),
                        TimeUnit.MINUTES.toMillis(15),
                        null);

        BackgroundTaskJobService jobService = new BackgroundTaskJobService();
        jobService.setClockForTesting(sClock);
        assertFalse(jobService.onStartJob(jobParameters));

        verify(mBackgroundTaskSchedulerUma, times(0)).reportTaskStarted(eq(TaskIds.TEST));
    }

    @Test
    @Feature({"BackgroundTaskScheduler"})
    public void testPeriodicTaskDoesNotStartAfterDeadline() {
        JobParameters jobParameters =
                buildPeriodicJobParameters(
                        TaskIds.TEST,
                        sClock.currentTimeMillis() - TimeUnit.MINUTES.toMillis(3),
                        TimeUnit.MINUTES.toMillis(15),
                        null);

        BackgroundTaskJobService jobService = new BackgroundTaskJobService();
        jobService.setClockForTesting(sClock);
        assertFalse(jobService.onStartJob(jobParameters));

        verify(mBackgroundTaskSchedulerUma, times(0)).reportTaskStarted(eq(TaskIds.TEST));
    }

    private static JobParameters newJobParameters(int jobId, PersistableBundle extras) {
        JobParameters ret = mock(JobParameters.class);
        when(ret.getJobId()).thenReturn(jobId);
        when(ret.getExtras()).thenReturn(extras);
        return ret;
    }

    private static JobParameters buildOneOffJobParameters(
            int taskId, Long schedulingTimeMs, Long windowEndTimeForDeadlineMs) {
        PersistableBundle extras = new PersistableBundle();
        if (schedulingTimeMs != null) {
            extras.putLong(
                    BackgroundTaskSchedulerDelegate.BACKGROUND_TASK_SCHEDULE_TIME_KEY,
                    schedulingTimeMs);
        }
        if (windowEndTimeForDeadlineMs != null) {
            extras.putLong(
                    BackgroundTaskSchedulerDelegate.BACKGROUND_TASK_END_TIME_KEY,
                    windowEndTimeForDeadlineMs);
        }
        PersistableBundle taskExtras = new PersistableBundle();
        extras.putPersistableBundle(
                BackgroundTaskSchedulerDelegate.BACKGROUND_TASK_EXTRAS_KEY, taskExtras);

        return newJobParameters(taskId, extras);
    }

    private static JobParameters buildPeriodicJobParameters(
            int taskId, Long schedulingTimeMs, Long intervalForDeadlineMs, Long flexForDeadlineMs) {
        PersistableBundle extras = new PersistableBundle();
        if (schedulingTimeMs != null) {
            extras.putLong(
                    BackgroundTaskSchedulerDelegate.BACKGROUND_TASK_SCHEDULE_TIME_KEY,
                    schedulingTimeMs);
            extras.putLong(
                    BackgroundTaskSchedulerDelegate.BACKGROUND_TASK_INTERVAL_TIME_KEY,
                    intervalForDeadlineMs);
            if (flexForDeadlineMs != null) {
                extras.putLong(
                        BackgroundTaskSchedulerDelegate.BACKGROUND_TASK_FLEX_TIME_KEY,
                        flexForDeadlineMs);
            }
        }
        PersistableBundle taskExtras = new PersistableBundle();
        extras.putPersistableBundle(
                BackgroundTaskSchedulerDelegate.BACKGROUND_TASK_EXTRAS_KEY, taskExtras);

        return newJobParameters(taskId, extras);
    }

    @Test
    @Feature({"BackgroundTaskScheduler"})
    @Config(sdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
    public void testSetNotification() {
        FakeBackgroundTask fakeBackgroundTask = new FakeBackgroundTask();
        BackgroundTaskSchedulerFactoryInternal.setBackgroundTaskFactory(
                new FakeBackgroundTaskFactory(fakeBackgroundTask));
        JobParameters jobParameters =
                buildOneOffJobParameters(
                        TaskIds.TEST, sClock.currentTimeMillis(), Long.valueOf(1000));

        BackgroundTaskJobService jobService = new BackgroundTaskJobService();
        jobService.setClockForTesting(sClock);
        assertTrue("onStartJob() didn't return true", jobService.onStartJob(jobParameters));
        fakeBackgroundTask.mTaskFinishedCallback.setNotification(22, null);
        fakeBackgroundTask.mTaskFinishedCallback.taskFinished(true);

        verify(mBackgroundTaskSchedulerUma, times(1)).reportTaskStarted(eq(TaskIds.TEST));
        verify(mBackgroundTaskSchedulerUma, times(1))
                .reportNotificationWasSet(eq(TaskIds.TEST), anyLong());
        verify(mBackgroundTaskSchedulerUma, times(1))
                .reportTaskFinished(eq(TaskIds.TEST), anyLong());
    }

    public static class FakeBackgroundTaskFactory implements BackgroundTaskFactory {
        private BackgroundTask mFakeBackgroundTask;

        FakeBackgroundTaskFactory(BackgroundTask fakeBackgroundTask) {
            mFakeBackgroundTask = fakeBackgroundTask;
        }

        @Override
        public BackgroundTask getBackgroundTaskFromTaskId(int taskId) {
            if (taskId == TaskIds.TEST) {
                return mFakeBackgroundTask;
            }
            return null;
        }
    }

    private static class FakeBackgroundTask implements BackgroundTask {
        private TaskFinishedCallback mTaskFinishedCallback;

        @Override
        public boolean onStartTask(
                Context context, TaskParameters taskParameters, TaskFinishedCallback callback) {
            mTaskFinishedCallback = callback;
            return true;
        }

        @Override
        public boolean onStopTask(Context context, TaskParameters taskParameters) {
            return false;
        }
    }
}