coding-blocks/CBOnlineApp

View on GitHub
app/src/main/java/com/codingblocks/cbonlineapp/workers/SectionDownloadService.kt

Summary

Maintainability
C
1 day
Test Coverage
package com.codingblocks.cbonlineapp.workers

import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.os.Environment
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.codingblocks.cbonlineapp.R
import com.codingblocks.cbonlineapp.database.ContentDao
import com.codingblocks.cbonlineapp.database.SectionWithContentsDao
import com.codingblocks.cbonlineapp.database.models.ContentModel
import com.codingblocks.cbonlineapp.database.models.SectionContentHolder
import com.codingblocks.cbonlineapp.util.*
import com.codingblocks.onlineapi.CBOnlineLib
import com.google.gson.JsonObject
import com.vdocipher.aegis.media.ErrorDescription
import com.vdocipher.aegis.offline.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.inject
import retrofit2.Response
import java.io.File

/**
 * A Foreground Service to download files
 */

class SectionDownloadService : Service(), VdoDownloadManager.EventListener {

    companion object {
        fun startService(context: Context, sectionId: String, attemptId: String) {
            val startIntent = Intent(context, SectionDownloadService::class.java)
            startIntent.putExtra(SECTION_ID, sectionId)
            startIntent.putExtra(RUN_ATTEMPT_ID, attemptId)
            ContextCompat.startForegroundService(context, startIntent)
        }

        fun stopService(context: Context) {
            val stopIntent = Intent(context, SectionDownloadService::class.java)
            context.stopService(stopIntent)
        }

        const val NOTIFICATION_ID = 10
        const val ACTION_STOP = "ACTION_STOP_FOREGROUND_SERVICE"
        private val downloadList = hashMapOf<String, SectionContentHolder.DownloadableContent>()
    }

    private val notificationManager by lazy {
        applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    }
    private val vdoDownloadManager by lazy {
        VdoDownloadManager.getInstance(applicationContext)
    }

    private lateinit var notification: NotificationCompat.Builder

    private val contentDao: ContentDao by inject()
    private val sectionWithContentsDao: SectionWithContentsDao by inject()

    var sectionId: String? = null
    var attemptId: String? = null
    var sectionName: String? = null

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        if (intent.action == ACTION_STOP) {
            stopServiceManually()
            notificationManager.cancel(NOTIFICATION_ID)
        } else {
            downloadList.clear()
            attemptId = intent.getStringExtra(RUN_ATTEMPT_ID)
            sectionId = intent.getStringExtra(SECTION_ID)
            GlobalScope.launch {
                createNotification(
                    withContext(Dispatchers.IO) {
                        sectionWithContentsDao.getVideoIdsWithSectionId(sectionId!!, attemptId!!)
                    }
                )
            }
        }
        return START_NOT_STICKY
    }

    private suspend fun createNotification(sectionList: List<SectionContentHolder.DownloadableContent>) {
        if (sectionList.isNotEmpty()) {
            sectionName = sectionList.first().name
            notification = NotificationCompat.Builder(applicationContext, DOWNLOAD_CHANNEL_ID).apply {
                setSmallIcon(R.drawable.ic_file_download)
                setContentTitle("Downloading $sectionName")
                setOnlyAlertOnce(true)
                setStyle(NotificationCompat.BigTextStyle().bigText("0 out of ${sectionList.size} downloaded"))
                setProgress(sectionList.size, 0, false)
                setOngoing(true)
            }
            startDownload(sectionList)
            val stopSelf = Intent(this, SectionDownloadService::class.java)
            stopSelf.action = ACTION_STOP
            val pStopSelf =
                PendingIntent.getService(this, 0, stopSelf, /*Stop Service*/PendingIntent.FLAG_CANCEL_CURRENT)
            notification.addAction(R.drawable.ic_pause_white_24dp, "Cancel", pStopSelf)
            startForeground(1, notification.build())
        } else {
            stopServiceManually()
        }
    }

    private fun stopServiceManually() {
        vdoDownloadManager.removeEventListener(this)
        stopForeground(true)
        stopSelf()
    }

    private suspend fun startDownload(list: List<SectionContentHolder.DownloadableContent>) {
        list.forEach { content ->
            val response: Response<JsonObject> = withContext(Dispatchers.IO) {
                CBOnlineLib.api.getOtp(content.videoId, content.sectionId, attemptId!!, true)
            }
            if (response.isSuccessful) {
                response.body()?.let {
                    downloadList[content.videoId] = (content)
                    val mOtp = it.get("otp").asString
                    val mPlaybackInfo = it.get("playbackInfo").asString
                    initializeDownload(mOtp, mPlaybackInfo, content.videoId)
                }
            }
        }
    }

    private fun initializeDownload(mOtp: String, mPlaybackInfo: String, videoId: String) {
        val optionsDownloader = OptionsDownloader()
        optionsDownloader.downloadOptionsWithOtp(
            mOtp, mPlaybackInfo,
            object : OptionsDownloader.Callback {
                override fun onOptionsReceived(options: DownloadOptions) {
                    // we have received the available download options
                    val selectionIndices = intArrayOf(0, 1)
                    val downloadSelections = DownloadSelections(options, selectionIndices)
                    var file = applicationContext.getExternalFilesDir(Environment.getDataDirectory().absolutePath)
                    val directories =
                        applicationContext.getExternalFilesDirs(Environment.getDataDirectory().absolutePath)
                    if (PreferenceHelper.getPrefs(applicationContext).SP_SD_CARD && directories.size > 1) {
                        file = directories[1]
                    } else {
                        PreferenceHelper.getPrefs(applicationContext).SP_SD_CARD = false
                    }
                    val folderFile = File(file, "/$videoId")
                    if (!folderFile.exists()) {
                        folderFile.mkdir()
                    }
                    val request =
                        DownloadRequest.Builder(downloadSelections, folderFile.absolutePath).build()
                    // enqueue request to VdoDownloadManager for download
                    try {
                        vdoDownloadManager.enqueue(request)
                        vdoDownloadManager.addEventListener(this@SectionDownloadService)
                    } catch (e: IllegalArgumentException) {
                    } catch (e: IllegalStateException) {
                    }
                }

                override fun onOptionsNotReceived(errDesc: ErrorDescription) {
                    Log.e("Service Error", "onOptionsNotReceived : $errDesc")
                }
            }
        )
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    /** This function will be invoked when the progress of any download changes*/
    override fun onChanged(p0: String?, p1: DownloadStatus?) {
        notification.apply {
            setStyle(
                NotificationCompat.BigTextStyle()
                    .bigText("${downloadList.filterValues { it.isDownloaded }.size} out of ${downloadList.size} downloaded( Current ${p1?.downloadPercent}% )")
            )
        }
        notificationManager.notify(1, notification.build())
    }

    override fun onDeleted(p0: String?) {
    }

    /**
     * This function will be invoked when the download fails
     * it will remove the files when may have been downloaded and got corrupted
     */
    override fun onFailed(videoId: String, p1: DownloadStatus?) {
        val folderFile =
            File(applicationContext.getExternalFilesDir(Environment.getDataDirectory().absolutePath), "/$videoId")
        downloadList.remove(videoId)
        FileUtils.deleteRecursive(folderFile)
    }

    override fun onQueued(p0: String?, p1: DownloadStatus?) {
    }

    /**
     * Updates the [ContentModel] along with updating the notification.
     */
    override fun onCompleted(videoId: String, p1: DownloadStatus?) {
        val data = downloadList[videoId]
        GlobalScope.launch(Dispatchers.IO) {
            contentDao.updateContent(data?.contentId ?: "", 1)
        }
        downloadList[videoId]?.isDownloaded = true
        notification.apply {
            setProgress(downloadList.size, downloadList.filterValues { it.isDownloaded }.size, false)
            setStyle(
                NotificationCompat.BigTextStyle()
                    .bigText("${downloadList.filterValues { it.isDownloaded }.size} out of ${downloadList.size} downloaded")
            )
        }
        notificationManager.notify(1, notification.build())
        if (downloadList.filterValues { !it.isDownloaded }.isEmpty()) {
            stopServiceManually()
        }
    }

    /** Creates a new notification after completion of the task*/
    private fun createCompletionNotification() {
        notification = NotificationCompat.Builder(applicationContext, DOWNLOAD_CHANNEL_ID).apply {
            setSmallIcon(R.drawable.ic_file_download)
            setContentTitle("Downloaded $sectionName")
            setOnlyAlertOnce(true)
            setLargeIcon(BitmapFactory.decodeResource(applicationContext.resources, R.mipmap.ic_launcher))
            setStyle(
                NotificationCompat.BigTextStyle()
                    .bigText("${downloadList.filterValues { it.isDownloaded }.size} out of ${downloadList.size} downloaded")
            )
        }
        notificationManager.notify(2, notification.build())
    }
}