From 6f182fcb4489191d1089ba0f35276d254ff02e91 Mon Sep 17 00:00:00 2001 From: Hong Ngoc Nguyen Date: Tue, 13 Feb 2024 16:36:09 -0500 Subject: [PATCH] [APT-9577] Add DRM for downloads for offline usage : enable DRM download/removal to execute in parallel to audio download/removal --- .../armadillo/download/DownloadEngine.kt | 58 ++++++++++++------- .../download/drm/OfflineDrmManager.kt | 17 +++--- 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadEngine.kt b/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadEngine.kt index 6bea4cb..8753dbd 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadEngine.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadEngine.kt @@ -22,8 +22,11 @@ import com.scribd.armadillo.extensions.toUri import com.scribd.armadillo.hasSnowCone import com.scribd.armadillo.models.AudioPlayable import com.scribd.armadillo.playback.createRenderersFactory +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import java.io.IOException import javax.inject.Inject +import javax.inject.Named import javax.inject.Singleton internal interface DownloadEngine { @@ -48,42 +51,53 @@ internal class ExoplayerDownloadEngine @Inject constructor( private val downloadTracker: DownloadTracker, private val stateModifier: StateStore.Modifier, private val offlineDrmManager: OfflineDrmManager, + @Named(Constants.DI.GLOBAL_SCOPE) private val globalScope: CoroutineScope, ) : DownloadEngine { override fun init() = downloadTracker.init() override fun download(audiobook: AudioPlayable) { - // Download DRM license for offline use - offlineDrmManager.downloadDrmLicenseForOffline(audiobook) + globalScope.launch { + launch { + // Download DRM license for offline use + offlineDrmManager.downloadDrmLicenseForOffline(audiobook) + } - val downloadHelper = downloadHelper(context, audiobook.request) + launch { + val downloadHelper = downloadHelper(context, audiobook.request) - downloadHelper.prepare(object : DownloadHelper.Callback { - override fun onPrepared(helper: DownloadHelper) { - val request = helper.getDownloadRequest(audiobook.id.encodeInByteArray()) - try { - startDownload(context, request) - } catch (e: Exception) { - if (hasSnowCone() && e is ForegroundServiceStartNotAllowedException) { - stateModifier.dispatch(ErrorAction(DownloadServiceLaunchedInBackground(audiobook.id))) - } else { - stateModifier.dispatch(ErrorAction(com.scribd.armadillo.error.ArmadilloIOException(e))) + downloadHelper.prepare(object : DownloadHelper.Callback { + override fun onPrepared(helper: DownloadHelper) { + val request = helper.getDownloadRequest(audiobook.id.encodeInByteArray()) + try { + startDownload(context, request) + } catch (e: Exception) { + if (hasSnowCone() && e is ForegroundServiceStartNotAllowedException) { + stateModifier.dispatch(ErrorAction(DownloadServiceLaunchedInBackground(audiobook.id))) + } else { + stateModifier.dispatch(ErrorAction(com.scribd.armadillo.error.ArmadilloIOException(e))) + } + } } - } - } - override fun onPrepareError(helper: DownloadHelper, e: IOException) = - stateModifier.dispatch(ErrorAction(com.scribd.armadillo.error.ArmadilloIOException(e))) - }) + override fun onPrepareError(helper: DownloadHelper, e: IOException) = + stateModifier.dispatch(ErrorAction(com.scribd.armadillo.error.ArmadilloIOException(e))) + }) + } + } } override fun removeDownload(audiobook: AudioPlayable) { - downloadManager.removeDownload(audiobook.request.url) - offlineDrmManager.removeDownloadedDrmLicense(audiobook) + globalScope.launch { + launch { downloadManager.removeDownload(audiobook.request.url) } + launch { offlineDrmManager.removeDownloadedDrmLicense(audiobook) } + } } override fun removeAllDownloads() { - downloadManager.removeAllDownloads() - offlineDrmManager.removeAllDownloadedDrmLicenses() + globalScope.launch { + launch { downloadManager.removeAllDownloads() } + launch { offlineDrmManager.removeAllDownloadedDrmLicenses() } + } } override fun updateProgress() = downloadTracker.updateProgress() diff --git a/Armadillo/src/main/java/com/scribd/armadillo/download/drm/OfflineDrmManager.kt b/Armadillo/src/main/java/com/scribd/armadillo/download/drm/OfflineDrmManager.kt index 8456488..7960d13 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/download/drm/OfflineDrmManager.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/download/drm/OfflineDrmManager.kt @@ -4,17 +4,15 @@ import android.content.Context import com.google.android.exoplayer2.C import com.google.android.exoplayer2.util.Log import com.google.android.exoplayer2.util.Util -import com.scribd.armadillo.Constants import com.scribd.armadillo.encryption.SecureStorage import com.scribd.armadillo.error.DrmContentTypeUnsupportedException import com.scribd.armadillo.extensions.toUri import com.scribd.armadillo.models.AudioPlayable -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope +import kotlinx.coroutines.withContext import javax.inject.Inject -import javax.inject.Named import javax.inject.Singleton /** @@ -23,7 +21,6 @@ import javax.inject.Singleton @Singleton internal class OfflineDrmManager @Inject constructor( private val context: Context, - @Named(Constants.DI.GLOBAL_SCOPE) private val globalScope: CoroutineScope, private val secureStorage: SecureStorage, private val dashDrmLicenseDownloader: DashDrmLicenseDownloader, ) { @@ -31,8 +28,8 @@ internal class OfflineDrmManager @Inject constructor( private const val TAG = "OfflineDrmManager" } - fun downloadDrmLicenseForOffline(audiobook: AudioPlayable) { - globalScope.launch(Dispatchers.IO) { + suspend fun downloadDrmLicenseForOffline(audiobook: AudioPlayable) { + withContext(Dispatchers.IO) { audiobook.request.drmInfo?.let { drmInfo -> val drmResult = when (@C.ContentType val type = Util.inferContentType(audiobook.request.url.toUri(), null)) { C.TYPE_DASH -> dashDrmLicenseDownloader @@ -50,8 +47,8 @@ internal class OfflineDrmManager @Inject constructor( } } - fun removeDownloadedDrmLicense(audiobook: AudioPlayable) { - globalScope.launch(Dispatchers.IO) { + suspend fun removeDownloadedDrmLicense(audiobook: AudioPlayable) { + withContext(Dispatchers.IO) { audiobook.request.drmInfo?.let { drmInfo -> secureStorage.getDrmDownload(context, audiobook.request.url, drmInfo.drmType)?.let { drmDownload -> // Remove the persisted download info immediately so audio playback would stop using the offline license @@ -67,8 +64,8 @@ internal class OfflineDrmManager @Inject constructor( } } - fun removeAllDownloadedDrmLicenses() { - globalScope.launch(Dispatchers.IO) { + suspend fun removeAllDownloadedDrmLicenses() { + withContext(Dispatchers.IO) { // Make sure that a removal fails, it won't affect the removal of other licenses supervisorScope { secureStorage.getAllDrmDownloads(context).forEach { drmDownloadPair ->