From 208aa3fab830827cf32690ad77d77632624acfbd Mon Sep 17 00:00:00 2001 From: Hong Ngoc Nguyen Date: Tue, 13 Feb 2024 16:58:13 -0500 Subject: [PATCH] [APT-9577] Add DRM for downloads for offline usage : code improvements : use injection for SharedPreferences and DrmSessionManagerProvider --- .../java/com/scribd/armadillo/Constants.kt | 3 ++ .../com/scribd/armadillo/di/DownloadModule.kt | 13 +++++ .../com/scribd/armadillo/di/PlaybackModule.kt | 6 +++ .../armadillo/encryption/SecureStorage.kt | 51 ++++++++----------- .../mediasource/DashMediaSourceGenerator.kt | 5 +- 5 files changed, 47 insertions(+), 31 deletions(-) diff --git a/Armadillo/src/main/java/com/scribd/armadillo/Constants.kt b/Armadillo/src/main/java/com/scribd/armadillo/Constants.kt index 91af7fb..496c195 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/Constants.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/Constants.kt @@ -44,6 +44,9 @@ object Constants { const val EXOPLAYER_CACHE_DIRECTORY = "exoplayer_cache_directory" const val GLOBAL_SCOPE = "global_scope" + + const val STANDARD_STORAGE = "standard_storage" + const val DRM_DOWNLOAD_STORAGE = "drm_download_storage" } internal object Exoplayer { diff --git a/Armadillo/src/main/java/com/scribd/armadillo/di/DownloadModule.kt b/Armadillo/src/main/java/com/scribd/armadillo/di/DownloadModule.kt index 170f50f..96c3036 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/di/DownloadModule.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/di/DownloadModule.kt @@ -1,6 +1,7 @@ package com.scribd.armadillo.di import android.content.Context +import android.content.SharedPreferences import com.google.android.exoplayer2.offline.DownloadManager import com.google.android.exoplayer2.offline.DownloadService import com.google.android.exoplayer2.offline.DownloaderFactory @@ -98,6 +99,18 @@ internal class DownloadModule { @Provides fun secureStorage(secureStorage: ArmadilloSecureStorage): SecureStorage = secureStorage + @Singleton + @Provides + @Named(Constants.DI.STANDARD_STORAGE) + fun standardStorage(context: Context): SharedPreferences = + context.getSharedPreferences("armadillo.storage", Context.MODE_PRIVATE) + + @Singleton + @Provides + @Named(Constants.DI.DRM_DOWNLOAD_STORAGE) + fun drmDownloadStorage(context: Context): SharedPreferences = + context.getSharedPreferences("armadillo.download.drm", Context.MODE_PRIVATE) + @Singleton @Provides fun downloadManagerFactory(downloadManagerFactory: ArmadilloDownloadManagerFactory): DownloadManagerFactory = downloadManagerFactory diff --git a/Armadillo/src/main/java/com/scribd/armadillo/di/PlaybackModule.kt b/Armadillo/src/main/java/com/scribd/armadillo/di/PlaybackModule.kt index 0c2d7de..308b230 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/di/PlaybackModule.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/di/PlaybackModule.kt @@ -2,6 +2,8 @@ package com.scribd.armadillo.di import android.app.Application import android.content.Context +import com.google.android.exoplayer2.drm.DefaultDrmSessionManagerProvider +import com.google.android.exoplayer2.drm.DrmSessionManagerProvider import com.scribd.armadillo.broadcast.ArmadilloNoisyReceiver import com.scribd.armadillo.broadcast.ArmadilloNoisySpeakerReceiver import com.scribd.armadillo.broadcast.ArmadilloNotificationDeleteReceiver @@ -68,4 +70,8 @@ internal class PlaybackModule { @Provides @Singleton fun drmMediaSourceHelper(drmMediaSourceHelperImpl: DrmMediaSourceHelperImpl): DrmMediaSourceHelper = drmMediaSourceHelperImpl + + @Provides + @Singleton + fun drmSessionManagerProvider(): DrmSessionManagerProvider = DefaultDrmSessionManagerProvider() } \ No newline at end of file diff --git a/Armadillo/src/main/java/com/scribd/armadillo/encryption/SecureStorage.kt b/Armadillo/src/main/java/com/scribd/armadillo/encryption/SecureStorage.kt index ce23b25..3f45e67 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/encryption/SecureStorage.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/encryption/SecureStorage.kt @@ -1,8 +1,10 @@ package com.scribd.armadillo.encryption import android.content.Context +import android.content.SharedPreferences import android.util.Base64 import android.util.Log +import com.scribd.armadillo.Constants import com.scribd.armadillo.models.DrmDownload import com.scribd.armadillo.models.DrmType import kotlinx.serialization.decodeFromString @@ -11,6 +13,7 @@ import kotlinx.serialization.json.Json import java.nio.charset.StandardCharsets import java.security.MessageDigest import javax.inject.Inject +import javax.inject.Named import javax.inject.Singleton internal interface SecureStorage { @@ -23,28 +26,28 @@ internal interface SecureStorage { } @Singleton -internal class ArmadilloSecureStorage @Inject constructor() : SecureStorage { - private companion object { +internal class ArmadilloSecureStorage @Inject constructor( + @Named(Constants.DI.STANDARD_STORAGE) private val standardStorage: SharedPreferences, + @Named(Constants.DI.DRM_DOWNLOAD_STORAGE) private val drmDownloadStorage: SharedPreferences, +) : SecureStorage { + companion object { const val DOWNLOAD_KEY = "download_key" const val STRING_LENGTH = 20 const val ALLOWED_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz0123456789" const val DEFAULT = "82YEDKqPBEqA2qAb4bUU" - const val STANDARD_STORAGE_FILENAME = "armadillo.storage" - const val DOWNLOAD_FILENAME = "armadillo.download" const val TAG = "SecureStorage" } override fun downloadSecretKey(context: Context): ByteArray { - val sharedPreferences = context.getSharedPreferences(STANDARD_STORAGE_FILENAME, Context.MODE_PRIVATE) - return if (sharedPreferences.contains(DOWNLOAD_KEY)) { - val storedKey = sharedPreferences.getString(DOWNLOAD_KEY, DEFAULT)!! + return if (standardStorage.contains(DOWNLOAD_KEY)) { + val storedKey = standardStorage.getString(DOWNLOAD_KEY, DEFAULT)!! if (storedKey == DEFAULT) { Log.e(TAG, "Storage Is Out of Alignment") } storedKey.toSecretByteArray } else { createRandomString().also { - sharedPreferences.edit().putString(DOWNLOAD_KEY, it).apply() + standardStorage.edit().putString(DOWNLOAD_KEY, it).apply() }.toSecretByteArray } } @@ -56,37 +59,27 @@ internal class ArmadilloSecureStorage @Inject constructor() : SecureStorage { } override fun saveDrmDownload(context: Context, audioUrl: String, drmDownload: DrmDownload) { - context.getSharedPreferences(DOWNLOAD_FILENAME, Context.MODE_PRIVATE).also { sharedPrefs -> - val key = getDrmDownloadKey(audioUrl, drmDownload.drmType) - val value = Base64.encodeToString(Json.encodeToString(drmDownload).toByteArray(StandardCharsets.UTF_8), Base64.NO_WRAP) - sharedPrefs.edit().putString(key, value).apply() - } + val key = getDrmDownloadKey(audioUrl, drmDownload.drmType) + val value = Base64.encodeToString(Json.encodeToString(drmDownload).toByteArray(StandardCharsets.UTF_8), Base64.NO_WRAP) + drmDownloadStorage.edit().putString(key, value).apply() } override fun getDrmDownload(context: Context, audioUrl: String, drmType: DrmType): DrmDownload? = - context.getSharedPreferences(DOWNLOAD_FILENAME, Context.MODE_PRIVATE).let { sharedPrefs -> - sharedPrefs.getString(getDrmDownloadKey(audioUrl, drmType), null)?.decodeToDrmDownload() - } + drmDownloadStorage.getString(getDrmDownloadKey(audioUrl, drmType), null)?.decodeToDrmDownload() override fun getAllDrmDownloads(context: Context): Map = - context.getSharedPreferences(DOWNLOAD_FILENAME, Context.MODE_PRIVATE).let { sharedPrefs -> - sharedPrefs.all.keys.mapNotNull { key -> - sharedPrefs.getString(key, null)?.let { drmResult -> - key to drmResult.decodeToDrmDownload() - } - }.toMap() - } + drmDownloadStorage.all.keys.mapNotNull { key -> + drmDownloadStorage.getString(key, null)?.let { drmResult -> + key to drmResult.decodeToDrmDownload() + } + }.toMap() override fun removeDrmDownload(context: Context, audioUrl: String, drmType: DrmType) { - context.getSharedPreferences(DOWNLOAD_FILENAME, Context.MODE_PRIVATE).also { sharedPrefs -> - sharedPrefs.edit().remove(getDrmDownloadKey(audioUrl, drmType)).apply() - } + drmDownloadStorage.edit().remove(getDrmDownloadKey(audioUrl, drmType)).apply() } override fun removeDrmDownload(context: Context, key: String) { - context.getSharedPreferences(DOWNLOAD_FILENAME, Context.MODE_PRIVATE).also { sharedPrefs -> - sharedPrefs.edit().remove(key).apply() - } + drmDownloadStorage.edit().remove(key).apply() } private val String.toSecretByteArray: ByteArray diff --git a/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/DashMediaSourceGenerator.kt b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/DashMediaSourceGenerator.kt index bef0a39..1b1c390 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/DashMediaSourceGenerator.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/DashMediaSourceGenerator.kt @@ -1,7 +1,7 @@ package com.scribd.armadillo.playback.mediasource import android.content.Context -import com.google.android.exoplayer2.drm.DefaultDrmSessionManagerProvider +import com.google.android.exoplayer2.drm.DrmSessionManagerProvider import com.google.android.exoplayer2.offline.Download import com.google.android.exoplayer2.offline.DownloadHelper import com.google.android.exoplayer2.source.MediaSource @@ -15,6 +15,7 @@ internal class DashMediaSourceGenerator @Inject constructor( private val mediaSourceHelper: HeadersMediaSourceHelper, private val downloadTracker: DownloadTracker, private val drmMediaSourceHelper: DrmMediaSourceHelper, + private val drmSessionManagerProvider: DrmSessionManagerProvider, ) : MediaSourceGenerator { override fun generateMediaSource(context: Context, request: AudioPlayable.MediaRequest): MediaSource { @@ -23,7 +24,7 @@ internal class DashMediaSourceGenerator @Inject constructor( downloadTracker.getDownload(request.url.toUri())?.let { if (it.state != Download.STATE_FAILED) { val mediaItem = drmMediaSourceHelper.createMediaItem(context = context, request = request, isDownload = true) - return DownloadHelper.createMediaSource(it.request, dataSourceFactory, DefaultDrmSessionManagerProvider().get(mediaItem)) + return DownloadHelper.createMediaSource(it.request, dataSourceFactory, drmSessionManagerProvider.get(mediaItem)) } }