chromium/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadForegroundServiceManagerTest.java

// Copyright 2017 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.chrome.browser.download;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import static org.chromium.chrome.browser.notifications.NotificationConstants.DEFAULT_NOTIFICATION_ID;

import android.app.Notification;
import android.content.Context;

import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.AdvancedMockContext;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.notifications.NotificationWrapperBuilderFactory;
import org.chromium.chrome.browser.notifications.channels.ChromeChannelDefinitions;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;

/** Test for DownloadForegroundServiceManager. */
@RunWith(ChromeJUnit4ClassRunner.class)
@Batch(Batch.UNIT_TESTS)
public final class DownloadForegroundServiceManagerTest {
    private static final int FAKE_DOWNLOAD_1 = 111;
    private static final int FAKE_DOWNLOAD_2 = 222;
    private static final int FAKE_DOWNLOAD_3 = 333;
    private static final String FAKE_NOTIFICATION_CHANNEL = "DownloadForegroundServiceManagerTest";

    private MockDownloadForegroundServiceManager mDownloadServiceManager;
    private Notification mNotification;
    private Context mContext;

    /**
     * Implementation of DownloadServiceManager for testing purposes. Generally mimics behavior of
     * DownloadForegroundServiceManager except: - Tracks a few variables for testing purposes
     * (mIsServiceBound, mUpdateNotificationId, etc). - Does not actually execute code related to
     * starting and stopping the service (startAndBindServiceInternal, etc) to not have have to
     * handle test service lifecycle.
     */
    public static class MockDownloadForegroundServiceManager
            extends DownloadForegroundServiceManager {
        private boolean mIsServiceBound;
        private int mUpdatedNotificationId = DEFAULT_NOTIFICATION_ID;
        private int mStopForegroundNotificationFlag = -1;

        public MockDownloadForegroundServiceManager() {}

        @Override
        boolean isEnabled() {
            return true;
        }

        @Override
        void startAndBindService(Context context) {
            mIsServiceBound = true;
            super.startAndBindService(context);
        }

        @Override
        void startAndBindServiceInternal(Context context) {}

        @Override
        void stopAndUnbindService(@DownloadNotificationService.DownloadStatus int downloadStatus) {
            mIsServiceBound = false;
            super.stopAndUnbindService(downloadStatus);
        }

        @Override
        void stopAndUnbindServiceInternal(
                @DownloadForegroundServiceImpl.StopForegroundNotification
                        int stopForegroundNotification,
                int pinnedNotificationId,
                Notification pinnedNotification) {
            mStopForegroundNotificationFlag = stopForegroundNotification;
        }

        @Override
        void startOrUpdateForegroundService(DownloadUpdate downloadUpdate) {
            mUpdatedNotificationId = downloadUpdate.mNotificationId;
            super.startOrUpdateForegroundService(downloadUpdate);
        }

        // Skip waiting for delayed runnable in tests.
        @Override
        void postMaybeStopServiceRunnable() {}

        @Override
        protected boolean canStartForeground() {
            return true;
        }

        /**
         * Call for testing that mimics the onServiceConnected call in mConnection that ensures the
         * mBoundService is non-null and the pending queue is processed.
         */
        void onServiceConnected() {
            setBoundService(new MockDownloadForegroundService());
            processDownloadUpdateQueue(/* isProcessingPending= */ true);
        }
    }

    /**
     * Implementation of DownloadForegroundService for testing. Does not implement
     * startOrUpdateForegroundService to avoid test service lifecycle.
     */
    public static class MockDownloadForegroundService extends DownloadForegroundServiceImpl {
        @Override
        public void startOrUpdateForegroundService(
                int newNotificationId,
                Notification newNotification,
                int oldNotificationId,
                Notification oldNotification,
                boolean killOldNotification) {}
    }

    @Before
    public void setUp() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mContext = new AdvancedMockContext(ApplicationProvider.getApplicationContext());
                    mDownloadServiceManager = new MockDownloadForegroundServiceManager();

                    mNotification =
                            NotificationWrapperBuilderFactory.createNotificationWrapperBuilder(
                                            ChromeChannelDefinitions.ChannelId.DOWNLOADS)
                                    .setSmallIcon(
                                            org.chromium.chrome.R.drawable
                                                    .ic_file_download_white_24dp)
                                    .setContentTitle(FAKE_NOTIFICATION_CHANNEL)
                                    .setContentText(FAKE_NOTIFICATION_CHANNEL)
                                    .build();
                });
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"Download"})
    public void testBasicStartAndStop() {
        // Service starts and stops with addition and removal of one active download.
        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.IN_PROGRESS,
                FAKE_DOWNLOAD_1,
                mNotification);
        assertTrue(mDownloadServiceManager.mIsServiceBound);
        mDownloadServiceManager.onServiceConnected();

        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.COMPLETED,
                FAKE_DOWNLOAD_1,
                mNotification);
        assertFalse(mDownloadServiceManager.mIsServiceBound);

        // Service does not get affected by addition of inactive download.
        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.IN_PROGRESS,
                FAKE_DOWNLOAD_1,
                mNotification);
        assertTrue(mDownloadServiceManager.mIsServiceBound);
        mDownloadServiceManager.onServiceConnected();

        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.PAUSED,
                FAKE_DOWNLOAD_2,
                mNotification);
        assertTrue(mDownloadServiceManager.mIsServiceBound);

        // Service continues as long as there is at least one active download.
        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.IN_PROGRESS,
                FAKE_DOWNLOAD_3,
                mNotification);
        assertTrue(mDownloadServiceManager.mIsServiceBound);
        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.PAUSED,
                FAKE_DOWNLOAD_1,
                mNotification);
        assertEquals(FAKE_DOWNLOAD_3, mDownloadServiceManager.mUpdatedNotificationId);
        assertTrue(mDownloadServiceManager.mIsServiceBound);
        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.COMPLETED,
                FAKE_DOWNLOAD_3,
                mNotification);
        assertFalse(mDownloadServiceManager.mIsServiceBound);
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"Download"})
    public void testDelayedStartStop() {
        // Calls to start and stop service.
        assertFalse(mDownloadServiceManager.mIsServiceBound);
        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.IN_PROGRESS,
                FAKE_DOWNLOAD_1,
                mNotification);
        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.COMPLETED,
                FAKE_DOWNLOAD_1,
                mNotification);

        assertTrue(mDownloadServiceManager.mIsServiceBound);

        // Service actually starts, should be shut down immediately.
        mDownloadServiceManager.onServiceConnected();
        assertFalse(mDownloadServiceManager.mIsServiceBound);
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"Download"})
    public void testDelayedStartStopStart() {
        // Calls to start and stop and start service.
        assertFalse(mDownloadServiceManager.mIsServiceBound);
        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.IN_PROGRESS,
                FAKE_DOWNLOAD_1,
                mNotification);
        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.COMPLETED,
                FAKE_DOWNLOAD_1,
                mNotification);
        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.IN_PROGRESS,
                FAKE_DOWNLOAD_2,
                mNotification);
        assertTrue(mDownloadServiceManager.mIsServiceBound);

        // Service actually starts, continues and is pinned to second download.
        mDownloadServiceManager.onServiceConnected();
        assertTrue(mDownloadServiceManager.mIsServiceBound);
        assertEquals(FAKE_DOWNLOAD_2, mDownloadServiceManager.mUpdatedNotificationId);

        // Make sure service is able to be shut down.
        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.COMPLETED,
                FAKE_DOWNLOAD_2,
                mNotification);
        assertFalse(mDownloadServiceManager.mIsServiceBound);
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"Download"})
    public void testIsNotificationKilledOrDetached() {
        // Service starts and is paused, not complete, so notification not killed but is detached.
        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.IN_PROGRESS,
                FAKE_DOWNLOAD_1,
                mNotification);
        assertTrue(mDownloadServiceManager.mIsServiceBound);
        mDownloadServiceManager.onServiceConnected();

        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.PAUSED,
                FAKE_DOWNLOAD_1,
                mNotification);
        assertFalse(mDownloadServiceManager.mIsServiceBound);
        assertEquals(
                DownloadForegroundServiceImpl.StopForegroundNotification.DETACH,
                mDownloadServiceManager.mStopForegroundNotificationFlag);

        // Service restarts and then is cancelled, so notification is killed.
        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.IN_PROGRESS,
                FAKE_DOWNLOAD_1,
                mNotification);
        assertTrue(mDownloadServiceManager.mIsServiceBound);
        mDownloadServiceManager.onServiceConnected();

        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.CANCELLED,
                FAKE_DOWNLOAD_1,
                mNotification);
        assertFalse(mDownloadServiceManager.mIsServiceBound);
        assertEquals(
                DownloadForegroundServiceImpl.StopForegroundNotification.KILL,
                mDownloadServiceManager.mStopForegroundNotificationFlag);

        // Download starts and completes, notification is either detached or killed.
        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.IN_PROGRESS,
                FAKE_DOWNLOAD_2,
                mNotification);
        assertTrue(mDownloadServiceManager.mIsServiceBound);
        mDownloadServiceManager.onServiceConnected();

        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.COMPLETED,
                FAKE_DOWNLOAD_2,
                mNotification);
        assertFalse(mDownloadServiceManager.mIsServiceBound);
        assertEquals(
                DownloadForegroundServiceImpl.StopForegroundNotification.DETACH,
                mDownloadServiceManager.mStopForegroundNotificationFlag);
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"Download"})
    public void testStopInitiallyAndCleanQueue() {
        // First call is a download being cancelled.
        assertFalse(mDownloadServiceManager.mIsServiceBound);
        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.CANCELLED,
                FAKE_DOWNLOAD_1,
                mNotification);

        // Make sure that nothing gets called, service is still not bound, and queue is empty.
        assertFalse(mDownloadServiceManager.mIsServiceBound);
        assertTrue(mDownloadServiceManager.mDownloadUpdateQueue.isEmpty());

        // Start next two downloads.
        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.IN_PROGRESS,
                FAKE_DOWNLOAD_2,
                mNotification);
        assertEquals(1, mDownloadServiceManager.mDownloadUpdateQueue.size());
        assertTrue(mDownloadServiceManager.mIsServiceBound);
        mDownloadServiceManager.onServiceConnected();

        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.IN_PROGRESS,
                FAKE_DOWNLOAD_3,
                mNotification);
        assertEquals(2, mDownloadServiceManager.mDownloadUpdateQueue.size());
        assertTrue(mDownloadServiceManager.mIsServiceBound);

        // Queue is cleaned as each download becomes inactive (paused or complete).
        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.PAUSED,
                FAKE_DOWNLOAD_2,
                mNotification);
        assertTrue(mDownloadServiceManager.mIsServiceBound);
        assertEquals(1, mDownloadServiceManager.mDownloadUpdateQueue.size());

        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.IN_PROGRESS,
                FAKE_DOWNLOAD_2,
                mNotification);
        assertTrue(mDownloadServiceManager.mIsServiceBound);
        assertEquals(2, mDownloadServiceManager.mDownloadUpdateQueue.size());

        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.COMPLETED,
                FAKE_DOWNLOAD_2,
                mNotification);
        assertTrue(mDownloadServiceManager.mIsServiceBound);
        assertEquals(1, mDownloadServiceManager.mDownloadUpdateQueue.size());

        mDownloadServiceManager.updateDownloadStatus(
                mContext,
                DownloadNotificationService.DownloadStatus.COMPLETED,
                FAKE_DOWNLOAD_3,
                mNotification);
        assertTrue(mDownloadServiceManager.mDownloadUpdateQueue.isEmpty());
        assertFalse(mDownloadServiceManager.mIsServiceBound);
    }
}