Skip to content

Commit

Permalink
sitepermissions: Handle EME permission.
Browse files Browse the repository at this point in the history
This allows Fenix to play EME content without having to toggle manually
`media.eme.require-app-approval=false` in `about:config`.

Fixes mozilla-mobile#7121
  • Loading branch information
emilio committed Dec 3, 2020
1 parent f43545f commit 9f3ab6e
Show file tree
Hide file tree
Showing 14 changed files with 186 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_AUTOPLAY
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_AUTOPLAY_INAUDIBLE
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_GEOLOCATION
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_MEDIA_KEY_SYSTEM_ACCESS
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_PERSISTENT_STORAGE
import java.util.UUID

Expand Down Expand Up @@ -59,7 +60,8 @@ sealed class GeckoPermissionRequest constructor(
PERMISSION_GEOLOCATION to Permission.ContentGeoLocation(),
PERMISSION_AUTOPLAY_AUDIBLE to Permission.ContentAutoPlayAudible(),
PERMISSION_AUTOPLAY_INAUDIBLE to Permission.ContentAutoPlayInaudible(),
PERMISSION_PERSISTENT_STORAGE to Permission.ContentPersistentStorage()
PERMISSION_PERSISTENT_STORAGE to Permission.ContentPersistentStorage(),
PERMISSION_MEDIA_KEY_SYSTEM_ACCESS to Permission.ContentMediaKeySystemAccess()
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_AUTOPLAY
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_AUTOPLAY_INAUDIBLE
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_GEOLOCATION
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_MEDIA_KEY_SYSTEM_ACCESS
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_PERSISTENT_STORAGE
import java.util.UUID

Expand Down Expand Up @@ -59,7 +60,8 @@ sealed class GeckoPermissionRequest constructor(
PERMISSION_GEOLOCATION to Permission.ContentGeoLocation(),
PERMISSION_AUTOPLAY_AUDIBLE to Permission.ContentAutoPlayAudible(),
PERMISSION_AUTOPLAY_INAUDIBLE to Permission.ContentAutoPlayInaudible(),
PERMISSION_PERSISTENT_STORAGE to Permission.ContentPersistentStorage()
PERMISSION_PERSISTENT_STORAGE to Permission.ContentPersistentStorage(),
PERMISSION_MEDIA_KEY_SYSTEM_ACCESS to Permission.ContentMediaKeySystemAccess()
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_AUTOPLAY
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_AUTOPLAY_INAUDIBLE
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_GEOLOCATION
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_MEDIA_KEY_SYSTEM_ACCESS
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_PERSISTENT_STORAGE
import java.util.UUID

Expand Down Expand Up @@ -59,7 +60,8 @@ sealed class GeckoPermissionRequest constructor(
PERMISSION_GEOLOCATION to Permission.ContentGeoLocation(),
PERMISSION_AUTOPLAY_AUDIBLE to Permission.ContentAutoPlayAudible(),
PERMISSION_AUTOPLAY_INAUDIBLE to Permission.ContentAutoPlayInaudible(),
PERMISSION_PERSISTENT_STORAGE to Permission.ContentPersistentStorage()
PERMISSION_PERSISTENT_STORAGE to Permission.ContentPersistentStorage(),
PERMISSION_MEDIA_KEY_SYSTEM_ACCESS to Permission.ContentMediaKeySystemAccess()
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ sealed class Permission {
data class ContentAutoPlayAudible(override val id: String? = "", override val desc: String? = "") : Permission()
data class ContentAutoPlayInaudible(override val id: String? = "", override val desc: String? = "") : Permission()
data class ContentPersistentStorage(override val id: String? = "", override val desc: String? = "") : Permission()
data class ContentMediaKeySystemAccess(override val id: String? = "", override val desc: String? = "") :
Permission()

data class AppCamera(override val id: String? = "", override val desc: String? = "") : Permission()
data class AppAudio(override val id: String? = "", override val desc: String? = "") : Permission()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "f5379c8eb4f1519eb5994e508626ca10",
"entities": [
{
"tableName": "site_permissions",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `location` INTEGER NOT NULL, `notification` INTEGER NOT NULL, `microphone` INTEGER NOT NULL, `camera` INTEGER NOT NULL, `bluetooth` INTEGER NOT NULL, `local_storage` INTEGER NOT NULL, `autoplay_audible` INTEGER NOT NULL, `autoplay_inaudible` INTEGER NOT NULL, `media_key_system_access` INTEGER NOT NULL, `saved_at` INTEGER NOT NULL, PRIMARY KEY(`origin`))",
"fields": [
{
"fieldPath": "origin",
"columnName": "origin",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "location",
"columnName": "location",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "notification",
"columnName": "notification",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "microphone",
"columnName": "microphone",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "camera",
"columnName": "camera",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "bluetooth",
"columnName": "bluetooth",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "localStorage",
"columnName": "local_storage",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "autoplayAudible",
"columnName": "autoplay_audible",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "autoplayInaudible",
"columnName": "autoplay_inaudible",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mediaKeySystemAccess",
"columnName": "media_key_system_access",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "savedAt",
"columnName": "saved_at",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"origin"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f5379c8eb4f1519eb5994e508626ca10')"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,29 @@ class OnDeviceSitePermissionsStorageTest {
assertEquals(Status.ALLOWED.id, cursor.getInt(cursor.getColumnIndexOrThrow("autoplay_inaudible")))
}
}

@Test
fun migrate3to4() {
helper.createDatabase(MIGRATION_TEST_DB, 3).apply {
query("SELECT * FROM site_permissions").use { cursor ->
assertEquals(10, cursor.columnCount)
}
execSQL(
"INSERT INTO " +
"site_permissions " +
"(origin, location, notification, microphone,camera,bluetooth,local_storage,autoplay_audible,autoplay_inaudible,saved_at) " +
"VALUES " +
"('mozilla.org',1,1,1,1,1,1,1,1,1)"
)
}

val dbVersion3 = helper.runMigrationsAndValidate(MIGRATION_TEST_DB, 4, true, Migrations.migration_3_4)

dbVersion3.query("SELECT * FROM site_permissions").use { cursor ->
assertEquals(11, cursor.columnCount)

cursor.moveToFirst()
assertEquals(Status.NO_DECISION.id, cursor.getInt(cursor.getColumnIndexOrThrow("media_key_system_access")))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ data class SitePermissions(
val localStorage: Status = NO_DECISION,
val autoplayAudible: Status = NO_DECISION,
val autoplayInaudible: Status = NO_DECISION,
val mediaKeySystemAccess: Status = NO_DECISION,
val savedAt: Long
) : Parcelable {
enum class Status(
Expand Down Expand Up @@ -53,6 +54,7 @@ data class SitePermissions(
Permission.LOCATION -> location
Permission.AUTOPLAY_AUDIBLE -> autoplayAudible
Permission.AUTOPLAY_INAUDIBLE -> autoplayInaudible
Permission.MEDIA_KEY_SYSTEM_ACCESS -> mediaKeySystemAccess
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import mozilla.components.concept.engine.permission.Permission.ContentNotificati
import mozilla.components.concept.engine.permission.Permission.ContentVideoCamera
import mozilla.components.concept.engine.permission.Permission.ContentVideoCapture
import mozilla.components.concept.engine.permission.Permission.ContentPersistentStorage
import mozilla.components.concept.engine.permission.Permission.ContentMediaKeySystemAccess
import mozilla.components.concept.engine.permission.Permission.AppLocationCoarse
import mozilla.components.concept.engine.permission.Permission.AppLocationFine
import mozilla.components.concept.engine.permission.Permission.AppAudio
Expand Down Expand Up @@ -442,6 +443,9 @@ class SitePermissionsFeature(
is ContentPersistentStorage -> {
permissionFromStore.localStorage.doNotAskAgain()
}
is ContentMediaKeySystemAccess -> {
permissionFromStore.mediaKeySystemAccess.doNotAskAgain()
}
else -> false
}
}
Expand Down Expand Up @@ -503,6 +507,9 @@ class SitePermissionsFeature(
is ContentPersistentStorage -> {
sitePermissions.copy(localStorage = status)
}
is ContentMediaKeySystemAccess -> {
sitePermissions.copy(mediaKeySystemAccess = status)
}
else ->
throw InvalidParameterException("$permission is not a valid permission.")
}
Expand Down Expand Up @@ -530,6 +537,7 @@ class SitePermissionsFeature(
}
}

@Suppress("LongMethod")
@VisibleForTesting
internal fun handlingSingleContentPermissions(
permissionRequest: PermissionRequest,
Expand Down Expand Up @@ -596,6 +604,17 @@ class SitePermissionsFeature(
shouldSelectRememberChoice = true
)
}
is ContentMediaKeySystemAccess -> {
createSinglePermissionPrompt(
context,
host,
permissionRequest,
R.string.mozac_feature_sitepermissions_media_key_system_access_title,
R.drawable.mozac_ic_video,
showDoNotAskAgainCheckBox = false,
shouldSelectRememberChoice = true
)
}
else ->
throw InvalidParameterException("$permission is not a valid permission.")
}
Expand Down Expand Up @@ -731,6 +750,9 @@ internal fun isPermissionGranted(
is ContentPersistentStorage -> {
permissionFromStorage.localStorage.isAllowed()
}
is ContentMediaKeySystemAccess -> {
permissionFromStorage.mediaKeySystemAccess.isAllowed()
}
else ->
throw InvalidParameterException("$permission is not a valid permission.")
}
Expand All @@ -743,7 +765,8 @@ private fun Permission.isSupported(): Boolean {
is ContentPersistentStorage,
is ContentAudioCapture, is ContentAudioMicrophone,
is ContentVideoCamera, is ContentVideoCapture,
is ContentAutoPlayAudible, is ContentAutoPlayInaudible -> true
is ContentAutoPlayAudible, is ContentAutoPlayInaudible,
is ContentMediaKeySystemAccess -> true
else -> false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import mozilla.components.feature.sitepermissions.SitePermissionsStorage.Permiss
import mozilla.components.feature.sitepermissions.SitePermissionsStorage.Permission.NOTIFICATION
import mozilla.components.feature.sitepermissions.SitePermissionsStorage.Permission.AUTOPLAY_AUDIBLE
import mozilla.components.feature.sitepermissions.SitePermissionsStorage.Permission.AUTOPLAY_INAUDIBLE
import mozilla.components.feature.sitepermissions.SitePermissionsStorage.Permission.MEDIA_KEY_SYSTEM_ACCESS
import mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase
import mozilla.components.feature.sitepermissions.db.toSitePermissionsEntity

Expand Down Expand Up @@ -116,6 +117,7 @@ class SitePermissionsStorage(
map.putIfAllowed(LOCATION, location, permission)
map.putIfAllowed(AUTOPLAY_AUDIBLE, autoplayAudible, permission)
map.putIfAllowed(AUTOPLAY_INAUDIBLE, autoplayInaudible, permission)
map.putIfAllowed(MEDIA_KEY_SYSTEM_ACCESS, mediaKeySystemAccess, permission)
}
}
return map
Expand Down Expand Up @@ -173,6 +175,6 @@ class SitePermissionsStorage(

enum class Permission {
MICROPHONE, BLUETOOTH, CAMERA, LOCAL_STORAGE, NOTIFICATION, LOCATION, AUTOPLAY_AUDIBLE,
AUTOPLAY_INAUDIBLE
AUTOPLAY_INAUDIBLE, MEDIA_KEY_SYSTEM_ACCESS
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import mozilla.components.feature.sitepermissions.SitePermissions
/**
* Internal database for saving site permissions.
*/
@Database(entities = [SitePermissionsEntity::class], version = 3)
@Database(entities = [SitePermissionsEntity::class], version = 4)
@TypeConverters(StatusConverter::class)
internal abstract class SitePermissionsDatabase : RoomDatabase() {
abstract fun sitePermissionsDao(): SitePermissionsDao
Expand All @@ -38,6 +38,8 @@ internal abstract class SitePermissionsDatabase : RoomDatabase() {
Migrations.migration_1_2
).addMigrations(
Migrations.migration_2_3
).addMigrations(
Migrations.migration_3_4
).build().also { instance = it }
}
}
Expand Down Expand Up @@ -98,4 +100,17 @@ internal object Migrations {
}
}
}

@Suppress("MagicNumber")
val migration_3_4 = object : Migration(3, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
val hasEmeColumn = database.query("SELECT * FROM site_permissions").columnCount == 11
if (!hasEmeColumn) {
database.execSQL(
"ALTER TABLE site_permissions ADD COLUMN media_key_system_access INTEGER NOT NULL DEFAULT 0")
// default is NO_DECISION
database.execSQL("UPDATE site_permissions SET media_key_system_access = 0")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ internal data class SitePermissionsEntity(
@ColumnInfo(name = "autoplay_inaudible")
var autoplayInaudible: SitePermissions.Status,

@ColumnInfo(name = "media_key_system_access")
var mediaKeySystemAccess: SitePermissions.Status,

@ColumnInfo(name = "saved_at")
var savedAt: Long
) {
Expand All @@ -58,6 +61,7 @@ internal data class SitePermissionsEntity(
localStorage,
autoplayAudible,
autoplayInaudible,
mediaKeySystemAccess,
savedAt
)
}
Expand All @@ -74,6 +78,7 @@ internal fun SitePermissions.toSitePermissionsEntity(): SitePermissionsEntity {
localStorage,
autoplayAudible,
autoplayInaudible,
mediaKeySystemAccess,
savedAt
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@
<string name="mozac_feature_sitepermissions_never_allow">Never</string>
<!-- Title of a dialog to require to save data in persistent storage. %1$s will be replaced with the URL of the current page -->
<string name="mozac_feature_sitepermissions_persistent_storage_title">Allow %1$s to store data in persistent storage?</string>
</resources>
<!-- Title of a dialog to use EME. %1$s will be replaced with the URL of the current page -->
<string name="mozac_feature_sitepermissions_media_key_system_access_title">Allow %1$s to play DRM-controlled content?</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ class SitePermissionsStorageTest {
bluetooth = ALLOWED,
autoplayAudible = BLOCKED,
autoplayInaudible = NO_DECISION,
mediaKeySystemAccess = NO_DECISION,
savedAt = 0
),
SitePermissionsEntity(
Expand All @@ -174,6 +175,7 @@ class SitePermissionsStorageTest {
bluetooth = ALLOWED,
autoplayAudible = BLOCKED,
autoplayInaudible = NO_DECISION,
mediaKeySystemAccess = NO_DECISION,
savedAt = 0
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class SitePermissionEntityTest {
bluetooth = ALLOWED,
autoplayInaudible = BLOCKED,
autoplayAudible = NO_DECISION,
mediaKeySystemAccess = NO_DECISION,
savedAt = 0
)

Expand Down

0 comments on commit 9f3ab6e

Please sign in to comment.