Skip to content

Commit

Permalink
[APT-9577] Add DRM for downloads for offline usage : code improvement…
Browse files Browse the repository at this point in the history
…s : use injection for SharedPreferences and DrmSessionManagerProvider
  • Loading branch information
rubeus90 committed Feb 13, 2024
1 parent 6f182fc commit 208aa3f
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 31 deletions.
3 changes: 3 additions & 0 deletions Armadillo/src/main/java/com/scribd/armadillo/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
13 changes: 13 additions & 0 deletions Armadillo/src/main/java/com/scribd/armadillo/di/DownloadModule.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -68,4 +70,8 @@ internal class PlaybackModule {
@Provides
@Singleton
fun drmMediaSourceHelper(drmMediaSourceHelperImpl: DrmMediaSourceHelperImpl): DrmMediaSourceHelper = drmMediaSourceHelperImpl

@Provides
@Singleton
fun drmSessionManagerProvider(): DrmSessionManagerProvider = DefaultDrmSessionManagerProvider()
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 {
Expand All @@ -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
}
}
Expand All @@ -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<String, DrmDownload> =
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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 {
Expand All @@ -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))
}
}

Expand Down

0 comments on commit 208aa3f

Please sign in to comment.