From def92d1b8e9d373e2f6f27c366d578d97d8960c6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:34:50 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- .../components/feature/sitepermissions/README.md | 61 + .../feature/sitepermissions/build.gradle | 88 ++ .../feature/sitepermissions/proguard-rules.pro | 21 + .../1.json | 81 ++ .../2.json | 75 + .../3.json | 88 ++ .../4.json | 94 ++ .../5.json | 94 ++ .../6.json | 94 ++ .../7.json | 94 ++ .../8.json | 100 ++ .../db/OnDeviceSitePermissionsStorageTest.kt | 230 ++++ .../sitepermissions/db/SitePermissionsDaoTest.kt | 112 ++ .../sitepermissions/src/main/AndroidManifest.xml | 4 + .../OnDiskSitePermissionsStorage.kt | 194 +++ .../SitePermissionsDialogFragment.kt | 258 ++++ .../sitepermissions/SitePermissionsFacts.kt | 65 + .../sitepermissions/SitePermissionsFeature.kt | 1057 ++++++++++++++ .../sitepermissions/SitePermissionsRules.kt | 126 ++ .../sitepermissions/db/SitePermissionsDao.kt | 40 + .../sitepermissions/db/SitePermissionsDatabase.kt | 189 +++ .../sitepermissions/db/SitePermissionsEntity.kt | 89 ++ .../res/layout/mozac_site_permissions_prompt.xml | 127 ++ .../src/main/res/values-am/strings.xml | 48 + .../src/main/res/values-an/strings.xml | 34 + .../src/main/res/values-ar/strings.xml | 48 + .../src/main/res/values-ast/strings.xml | 48 + .../src/main/res/values-az/strings.xml | 36 + .../src/main/res/values-azb/strings.xml | 48 + .../src/main/res/values-be/strings.xml | 48 + .../src/main/res/values-bg/strings.xml | 48 + .../src/main/res/values-bn/strings.xml | 38 + .../src/main/res/values-br/strings.xml | 48 + .../src/main/res/values-bs/strings.xml | 48 + .../src/main/res/values-ca/strings.xml | 48 + .../src/main/res/values-cak/strings.xml | 48 + .../src/main/res/values-ceb/strings.xml | 38 + .../src/main/res/values-ckb/strings.xml | 38 + .../src/main/res/values-co/strings.xml | 48 + .../src/main/res/values-cs/strings.xml | 48 + .../src/main/res/values-cy/strings.xml | 48 + .../src/main/res/values-da/strings.xml | 48 + .../src/main/res/values-de/strings.xml | 57 + .../src/main/res/values-dsb/strings.xml | 48 + .../src/main/res/values-el/strings.xml | 48 + .../src/main/res/values-en-rCA/strings.xml | 48 + .../src/main/res/values-en-rGB/strings.xml | 48 + .../src/main/res/values-eo/strings.xml | 48 + .../src/main/res/values-es-rAR/strings.xml | 48 + .../src/main/res/values-es-rCL/strings.xml | 48 + .../src/main/res/values-es-rES/strings.xml | 48 + .../src/main/res/values-es-rMX/strings.xml | 48 + .../src/main/res/values-es/strings.xml | 48 + .../src/main/res/values-et/strings.xml | 48 + .../src/main/res/values-eu/strings.xml | 48 + .../src/main/res/values-fa/strings.xml | 48 + .../src/main/res/values-ff/strings.xml | 36 + .../src/main/res/values-fi/strings.xml | 48 + .../src/main/res/values-fr/strings.xml | 48 + .../src/main/res/values-fur/strings.xml | 48 + .../src/main/res/values-fy-rNL/strings.xml | 48 + .../src/main/res/values-ga-rIE/strings.xml | 34 + .../src/main/res/values-gd/strings.xml | 48 + .../src/main/res/values-gl/strings.xml | 48 + .../src/main/res/values-gn/strings.xml | 48 + .../src/main/res/values-gu-rIN/strings.xml | 34 + .../src/main/res/values-hi-rIN/strings.xml | 34 + .../src/main/res/values-hil/strings.xml | 6 + .../src/main/res/values-hr/strings.xml | 48 + .../src/main/res/values-hsb/strings.xml | 48 + .../src/main/res/values-hu/strings.xml | 48 + .../src/main/res/values-hy-rAM/strings.xml | 48 + .../src/main/res/values-ia/strings.xml | 48 + .../src/main/res/values-in/strings.xml | 48 + .../src/main/res/values-is/strings.xml | 48 + .../src/main/res/values-it/strings.xml | 48 + .../src/main/res/values-iw/strings.xml | 48 + .../src/main/res/values-ja/strings.xml | 57 + .../src/main/res/values-ka/strings.xml | 48 + .../src/main/res/values-kaa/strings.xml | 48 + .../src/main/res/values-kab/strings.xml | 48 + .../src/main/res/values-kk/strings.xml | 48 + .../src/main/res/values-kmr/strings.xml | 48 + .../src/main/res/values-kn/strings.xml | 34 + .../src/main/res/values-ko/strings.xml | 48 + .../src/main/res/values-lij/strings.xml | 34 + .../src/main/res/values-lo/strings.xml | 48 + .../src/main/res/values-lt/strings.xml | 48 + .../src/main/res/values-ml/strings.xml | 34 + .../src/main/res/values-mr/strings.xml | 34 + .../src/main/res/values-my/strings.xml | 38 + .../src/main/res/values-nb-rNO/strings.xml | 48 + .../src/main/res/values-ne-rNP/strings.xml | 46 + .../src/main/res/values-nl/strings.xml | 48 + .../src/main/res/values-nn-rNO/strings.xml | 48 + .../src/main/res/values-oc/strings.xml | 48 + .../src/main/res/values-or/strings.xml | 12 + .../src/main/res/values-pa-rIN/strings.xml | 48 + .../src/main/res/values-pa-rPK/strings.xml | 48 + .../src/main/res/values-pl/strings.xml | 48 + .../src/main/res/values-pt-rBR/strings.xml | 48 + .../src/main/res/values-pt-rPT/strings.xml | 48 + .../src/main/res/values-rm/strings.xml | 48 + .../src/main/res/values-ro/strings.xml | 34 + .../src/main/res/values-ru/strings.xml | 48 + .../src/main/res/values-sat/strings.xml | 48 + .../src/main/res/values-sc/strings.xml | 45 + .../src/main/res/values-si/strings.xml | 48 + .../src/main/res/values-sk/strings.xml | 48 + .../src/main/res/values-skr/strings.xml | 48 + .../src/main/res/values-sl/strings.xml | 48 + .../src/main/res/values-sq/strings.xml | 48 + .../src/main/res/values-sr/strings.xml | 48 + .../src/main/res/values-su/strings.xml | 48 + .../src/main/res/values-sv-rSE/strings.xml | 48 + .../src/main/res/values-ta/strings.xml | 36 + .../src/main/res/values-te/strings.xml | 42 + .../src/main/res/values-tg/strings.xml | 48 + .../src/main/res/values-th/strings.xml | 48 + .../src/main/res/values-tl/strings.xml | 42 + .../src/main/res/values-tr/strings.xml | 48 + .../src/main/res/values-trs/strings.xml | 48 + .../src/main/res/values-tt/strings.xml | 42 + .../src/main/res/values-tzm/strings.xml | 30 + .../src/main/res/values-ug/strings.xml | 48 + .../src/main/res/values-uk/strings.xml | 48 + .../src/main/res/values-ur/strings.xml | 38 + .../src/main/res/values-uz/strings.xml | 48 + .../src/main/res/values-vec/strings.xml | 34 + .../src/main/res/values-vi/strings.xml | 48 + .../src/main/res/values-yo/strings.xml | 48 + .../src/main/res/values-zh-rCN/strings.xml | 57 + .../src/main/res/values-zh-rTW/strings.xml | 57 + .../src/main/res/values/strings.xml | 51 + .../OnDiskSitePermissionsStorageTest.kt | 251 ++++ .../SitePermissionsDialogFragmentTest.kt | 481 +++++++ .../sitepermissions/SitePermissionsFactsTest.kt | 116 ++ .../sitepermissions/SitePermissionsFeatureTest.kt | 1450 ++++++++++++++++++++ .../sitepermissions/SitePermissionsRulesTest.kt | 199 +++ .../feature/sitepermissions/SitePermissionsTest.kt | 88 ++ .../sitepermissions/db/SitePermissionEntityTest.kt | 86 ++ .../sitepermissions/db/StatusConverterTest.kt | 72 + .../org.mockito.plugins.MockMaker | 2 + .../src/test/resources/robolectric.properties | 1 + 144 files changed, 11149 insertions(+) create mode 100644 mobile/android/android-components/components/feature/sitepermissions/README.md create mode 100644 mobile/android/android-components/components/feature/sitepermissions/build.gradle create mode 100644 mobile/android/android-components/components/feature/sitepermissions/proguard-rules.pro create mode 100644 mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/1.json create mode 100644 mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/2.json create mode 100644 mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/3.json create mode 100644 mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/4.json create mode 100644 mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/5.json create mode 100644 mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/6.json create mode 100644 mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/7.json create mode 100644 mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/8.json create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/androidTest/java/mozilla/components/feature/sitepermissions/db/OnDeviceSitePermissionsStorageTest.kt create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/androidTest/java/mozilla/components/feature/sitepermissions/db/SitePermissionsDaoTest.kt create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/AndroidManifest.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/OnDiskSitePermissionsStorage.kt create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/SitePermissionsDialogFragment.kt create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/SitePermissionsFacts.kt create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/SitePermissionsFeature.kt create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/SitePermissionsRules.kt create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/db/SitePermissionsDao.kt create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/db/SitePermissionsDatabase.kt create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/db/SitePermissionsEntity.kt create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/layout/mozac_site_permissions_prompt.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-am/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-an/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-ar/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-ast/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-az/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-azb/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-be/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-bg/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-bn/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-br/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-bs/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-ca/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-cak/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-ceb/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-ckb/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-co/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-cs/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-cy/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-da/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-de/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-dsb/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-el/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-en-rCA/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-en-rGB/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-eo/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-es-rAR/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-es-rCL/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-es-rES/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-es-rMX/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-es/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-et/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-eu/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-fa/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-ff/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-fi/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-fr/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-fur/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-fy-rNL/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-ga-rIE/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-gd/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-gl/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-gn/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-gu-rIN/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-hi-rIN/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-hil/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-hr/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-hsb/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-hu/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-hy-rAM/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-ia/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-in/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-is/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-it/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-iw/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-ja/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-ka/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-kaa/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-kab/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-kk/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-kmr/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-kn/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-ko/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-lij/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-lo/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-lt/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-ml/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-mr/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-my/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-nb-rNO/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-ne-rNP/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-nl/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-nn-rNO/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-oc/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-or/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-pa-rIN/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-pa-rPK/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-pl/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-pt-rBR/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-pt-rPT/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-rm/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-ro/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-ru/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-sat/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-sc/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-si/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-sk/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-skr/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-sl/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-sq/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-sr/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-su/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-sv-rSE/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-ta/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-te/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-tg/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-th/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-tl/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-tr/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-trs/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-tt/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-tzm/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-ug/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-uk/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-ur/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-uz/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-vec/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-vi/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-yo/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-zh-rCN/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values-zh-rTW/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/main/res/values/strings.xml create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/OnDiskSitePermissionsStorageTest.kt create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/SitePermissionsDialogFragmentTest.kt create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/SitePermissionsFactsTest.kt create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/SitePermissionsFeatureTest.kt create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/SitePermissionsRulesTest.kt create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/SitePermissionsTest.kt create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/db/SitePermissionEntityTest.kt create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/db/StatusConverterTest.kt create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker create mode 100644 mobile/android/android-components/components/feature/sitepermissions/src/test/resources/robolectric.properties (limited to 'mobile/android/android-components/components/feature/sitepermissions') diff --git a/mobile/android/android-components/components/feature/sitepermissions/README.md b/mobile/android/android-components/components/feature/sitepermissions/README.md new file mode 100644 index 0000000000..5eefdb579c --- /dev/null +++ b/mobile/android/android-components/components/feature/sitepermissions/README.md @@ -0,0 +1,61 @@ +# [Android Components](../../../README.md) > Feature > Site Permissions + +A feature for showing site permission request prompts. + +## Usage + +### Setting up the dependency + +Use Gradle to download the library from [maven.mozilla.org](https://maven.mozilla.org/) ([Setup repository](../../../README.md#maven-repository)): + +```Groovy +implementation "org.mozilla.components:feature-sitepermissions:{latest-version}" +``` + +### SitePermissionsFeature + + ``` + Add these permissions to your ``AndroidManifest.xml`` file. + ```XML + + + + + ``` + + ```kotlin + val onNeedToRequestPermissions : (Array) -> Unit = { permissions -> + /* You are in charge of triggering the request for the permissions needed, + * this way you can control, when you request the permissions, + * in case that you want to show an informative dialog, + * to clarify the use of these permissions. + */ + this.requestPermissions(permissions, REQUEST_CODE_APP_PERMISSIONS) + } + + val sitePermissionsFeature = SitePermissionsFeature( + anchorView = toolbar, + sessionManager = components.sessionManager, + fragmentManager = requireFragmentManager(), + onNeedToRequestPermissions = onNeedToRequestPermissions + ) + + // It will start listing for new permissionRequest. + sitePermissionsFeature.start() + + // It will stop listing for new permissionRequest. + sitePermissionsFeature.stop() + + + // Notify the feature if the permissions requested were granted or rejected. + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + when (requestCode) { + REQUEST_CODE_APP_PERMISSIONS -> sitePermissionsFeature.onPermissionsResult(grantResults) + } + } + +## License + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/ diff --git a/mobile/android/android-components/components/feature/sitepermissions/build.gradle b/mobile/android/android-components/components/feature/sitepermissions/build.gradle new file mode 100644 index 0000000000..06fa58246b --- /dev/null +++ b/mobile/android/android-components/components/feature/sitepermissions/build.gradle @@ -0,0 +1,88 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-parcelize' + +apply plugin: 'com.google.devtools.ksp' + +android { + defaultConfig { + minSdkVersion config.minSdkVersion + compileSdk config.compileSdkVersion + targetSdkVersion config.targetSdkVersion + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + ksp { + arg("room.schemaLocation", "$projectDir/schemas".toString()) + arg("room.generateKotlin", "true") + } + + javaCompileOptions { + annotationProcessorOptions { + arguments += ["room.incremental": "true"] + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + packagingOptions { + exclude 'META-INF/proguard/androidx-annotations.pro' + } + + sourceSets { + androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) + } + + namespace 'mozilla.components.feature.sitepermissions' +} + +tasks.withType(KotlinCompile).configureEach { + kotlinOptions.freeCompilerArgs += "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi" +} + +dependencies { + implementation project(':browser-state') + implementation project(':concept-engine') + implementation project(':ui-icons') + implementation project(':support-ktx') + implementation project(':feature-tabs') + + implementation ComponentsDependencies.kotlin_coroutines + + implementation ComponentsDependencies.androidx_core_ktx + implementation ComponentsDependencies.androidx_constraintlayout + implementation ComponentsDependencies.androidx_lifecycle_livedata + implementation ComponentsDependencies.androidx_paging + implementation ComponentsDependencies.androidx_room_runtime + ksp ComponentsDependencies.androidx_room_compiler + + testImplementation project(':support-test') + + testImplementation ComponentsDependencies.androidx_test_core + testImplementation ComponentsDependencies.androidx_test_junit + testImplementation ComponentsDependencies.testing_robolectric + testImplementation ComponentsDependencies.testing_coroutines + + androidTestImplementation project(':support-android-test') + + androidTestImplementation ComponentsDependencies.androidx_room_testing + androidTestImplementation ComponentsDependencies.androidx_test_core + androidTestImplementation ComponentsDependencies.androidx_test_runner + androidTestImplementation ComponentsDependencies.androidx_test_rules + androidTestImplementation ComponentsDependencies.testing_coroutines +} + +apply from: '../../../android-lint.gradle' +apply from: '../../../publish.gradle' +ext.configurePublish(config.componentsGroupId, archivesBaseName, project.ext.description) diff --git a/mobile/android/android-components/components/feature/sitepermissions/proguard-rules.pro b/mobile/android/android-components/components/feature/sitepermissions/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/mobile/android/android-components/components/feature/sitepermissions/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/1.json b/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/1.json new file mode 100644 index 0000000000..1363bf43de --- /dev/null +++ b/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/1.json @@ -0,0 +1,81 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "462054044d4b7f0e4f80f84380d5cc1e", + "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_back` INTEGER NOT NULL, `camera_front` INTEGER NOT NULL, `bluetooth` INTEGER NOT NULL, `local_storage` 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": "cameraBack", + "columnName": "camera_back", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cameraFront", + "columnName": "camera_front", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "bluetooth", + "columnName": "bluetooth", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localStorage", + "columnName": "local_storage", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "savedAt", + "columnName": "saved_at", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "origin" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "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, \"462054044d4b7f0e4f80f84380d5cc1e\")" + ] + } +} diff --git a/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/2.json b/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/2.json new file mode 100644 index 0000000000..344aaccc30 --- /dev/null +++ b/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/2.json @@ -0,0 +1,75 @@ +{ + "formatVersion": 1, + "database": { + "version": 2, + "identityHash": "279719818fbc84cac905ddf942282eae", + "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, `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": "savedAt", + "columnName": "saved_at", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "origin" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "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, \"279719818fbc84cac905ddf942282eae\")" + ] + } +} diff --git a/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/3.json b/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/3.json new file mode 100644 index 0000000000..bfc2906dc9 --- /dev/null +++ b/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/3.json @@ -0,0 +1,88 @@ +{ + "formatVersion": 1, + "database": { + "version": 3, + "identityHash": "f5350dbda12da0f415e9d09d00c36f49", + "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, `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": "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, 'f5350dbda12da0f415e9d09d00c36f49')" + ] + } +} diff --git a/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/4.json b/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/4.json new file mode 100644 index 0000000000..44e6233c31 --- /dev/null +++ b/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/4.json @@ -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')" + ] + } +} diff --git a/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/5.json b/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/5.json new file mode 100644 index 0000000000..c570ac0037 --- /dev/null +++ b/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/5.json @@ -0,0 +1,94 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "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')" + ] + } +} diff --git a/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/6.json b/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/6.json new file mode 100644 index 0000000000..5780fd467f --- /dev/null +++ b/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/6.json @@ -0,0 +1,94 @@ +{ + "formatVersion": 1, + "database": { + "version": 6, + "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')" + ] + } +} diff --git a/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/7.json b/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/7.json new file mode 100644 index 0000000000..87a2a06f6a --- /dev/null +++ b/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/7.json @@ -0,0 +1,94 @@ +{ + "formatVersion": 1, + "database": { + "version": 7, + "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')" + ] + } +} diff --git a/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/8.json b/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/8.json new file mode 100644 index 0000000000..97a9421bd1 --- /dev/null +++ b/mobile/android/android-components/components/feature/sitepermissions/schemas/mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase/8.json @@ -0,0 +1,100 @@ +{ + "formatVersion": 1, + "database": { + "version": 8, + "identityHash": "a4391f9f5b2a6448070c7f5cefb1b086", + "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, `cross_origin_storage_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": "crossOriginStorageAccess", + "columnName": "cross_origin_storage_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, 'a4391f9f5b2a6448070c7f5cefb1b086')" + ] + } +} diff --git a/mobile/android/android-components/components/feature/sitepermissions/src/androidTest/java/mozilla/components/feature/sitepermissions/db/OnDeviceSitePermissionsStorageTest.kt b/mobile/android/android-components/components/feature/sitepermissions/src/androidTest/java/mozilla/components/feature/sitepermissions/db/OnDeviceSitePermissionsStorageTest.kt new file mode 100644 index 0000000000..83b2a5a042 --- /dev/null +++ b/mobile/android/android-components/components/feature/sitepermissions/src/androidTest/java/mozilla/components/feature/sitepermissions/db/OnDeviceSitePermissionsStorageTest.kt @@ -0,0 +1,230 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.sitepermissions.db + +import android.content.Context +import androidx.core.net.toUri +import androidx.room.Room +import androidx.room.testing.MigrationTestHelper +import androidx.test.core.app.ApplicationProvider +import androidx.test.platform.app.InstrumentationRegistry +import kotlinx.coroutines.test.runTest +import mozilla.components.concept.engine.permission.SitePermissions +import mozilla.components.concept.engine.permission.SitePermissions.AutoplayStatus +import mozilla.components.concept.engine.permission.SitePermissions.Status +import mozilla.components.feature.sitepermissions.OnDiskSitePermissionsStorage +import mozilla.components.support.ktx.kotlin.getOrigin +import mozilla.components.support.ktx.kotlin.tryGetHostFromUrl +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +private const val MIGRATION_TEST_DB = "migration-test" + +class OnDeviceSitePermissionsStorageTest { + private val context: Context + get() = ApplicationProvider.getApplicationContext() + + private lateinit var storage: OnDiskSitePermissionsStorage + private lateinit var database: SitePermissionsDatabase + + @get:Rule + val helper: MigrationTestHelper = MigrationTestHelper( + InstrumentationRegistry.getInstrumentation(), + SitePermissionsDatabase::class.java, + ) + + @Before + fun setUp() { + database = Room.inMemoryDatabaseBuilder(context, SitePermissionsDatabase::class.java).build() + storage = OnDiskSitePermissionsStorage(context) + storage.databaseInitializer = { + database + } + } + + @After + fun tearDown() { + database.close() + } + + @Test + fun testStorageInteraction() = runTest { + val origin = "https://www.mozilla.org".toUri().host!! + val sitePermissions = SitePermissions( + origin = origin, + camera = Status.BLOCKED, + savedAt = System.currentTimeMillis(), + ) + storage.save(sitePermissions, private = false) + val sitePermissionsFromStorage = storage.findSitePermissionsBy(origin, private = false)!! + + assertEquals(origin, sitePermissionsFromStorage.origin) + assertEquals(Status.BLOCKED, sitePermissionsFromStorage.camera) + } + + @Test + fun migrate1to2() { + val dbVersion1 = helper.createDatabase(MIGRATION_TEST_DB, 1).apply { + execSQL( + "INSERT INTO " + + "site_permissions " + + "(origin, location, notification, microphone,camera_front,camera_back,bluetooth,local_storage,saved_at) " + + "VALUES " + + "('mozilla.org',1,1,1,1,1,1,1,1)", + ) + } + + dbVersion1.query("SELECT * FROM site_permissions").use { cursor -> + assertEquals(9, cursor.columnCount) + } + + val dbVersion2 = helper.runMigrationsAndValidate( + MIGRATION_TEST_DB, + 2, + true, + Migrations.migration_1_2, + ).apply { + execSQL( + "INSERT INTO " + + "site_permissions " + + "(origin, location, notification, microphone,camera,bluetooth,local_storage,saved_at) " + + "VALUES " + + "('mozilla.org',1,1,1,1,1,1,1)", + ) + } + + dbVersion2.query("SELECT * FROM site_permissions").use { cursor -> + assertEquals(8, cursor.columnCount) + + cursor.moveToFirst() + assertEquals(1, cursor.getInt(cursor.getColumnIndexOrThrow("camera"))) + } + } + + @Test + fun migrate2to3() { + helper.createDatabase(MIGRATION_TEST_DB, 2).apply { + query("SELECT * FROM site_permissions").use { cursor -> + assertEquals(8, cursor.columnCount) + } + execSQL( + "INSERT INTO " + + "site_permissions " + + "(origin, location, notification, microphone,camera,bluetooth,local_storage,saved_at) " + + "VALUES " + + "('mozilla.org',1,1,1,1,1,1,1)", + ) + } + + val dbVersion3 = helper.runMigrationsAndValidate(MIGRATION_TEST_DB, 3, true, Migrations.migration_2_3) + + dbVersion3.query("SELECT * FROM site_permissions").use { cursor -> + assertEquals(10, cursor.columnCount) + + cursor.moveToFirst() + assertEquals(Status.BLOCKED.id, cursor.getInt(cursor.getColumnIndexOrThrow("autoplay_audible"))) + 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"))) + } + } + + @Test + fun migrate4to5() { + helper.createDatabase(MIGRATION_TEST_DB, 4).apply { + execSQL( + "INSERT INTO " + + "site_permissions " + + "(origin, location, notification, microphone,camera,bluetooth,local_storage,autoplay_audible,autoplay_inaudible,media_key_system_access,saved_at) " + + "VALUES " + + "('mozilla.org',1,1,1,1,1,1,0,0,1,1)", + ) + } + + val dbVersion5 = helper.runMigrationsAndValidate(MIGRATION_TEST_DB, 5, true, Migrations.migration_4_5) + + dbVersion5.query("SELECT * FROM site_permissions").use { cursor -> + cursor.moveToFirst() + assertEquals(AutoplayStatus.BLOCKED.id, cursor.getInt(cursor.getColumnIndexOrThrow("autoplay_audible"))) + assertEquals(AutoplayStatus.ALLOWED.id, cursor.getInt(cursor.getColumnIndexOrThrow("autoplay_inaudible"))) + } + } + + @Test + fun migrate5to6() { + val url = "https://permission.site/" + + helper.createDatabase(MIGRATION_TEST_DB, 5).apply { + execSQL( + "INSERT INTO " + + "site_permissions " + + "(origin, location, notification, microphone,camera,bluetooth,local_storage,autoplay_audible,autoplay_inaudible,media_key_system_access,saved_at) " + + "VALUES " + + "('${url.tryGetHostFromUrl()}',1,1,1,1,1,1,0,0,1,1)", + ) + } + + val dbVersion6 = + helper.runMigrationsAndValidate(MIGRATION_TEST_DB, 6, true, Migrations.migration_5_6) + + dbVersion6.query("SELECT * FROM site_permissions").use { cursor -> + cursor.moveToFirst() + val urlDB = cursor.getString(cursor.getColumnIndexOrThrow("origin")) + assertEquals(url.getOrigin(), urlDB) + } + } + + @Test + fun migrate6to7() { + val url = "https://permission.site/" + + helper.createDatabase(MIGRATION_TEST_DB, 6).apply { + execSQL( + "INSERT INTO " + + "site_permissions " + + "(origin, location, notification, microphone,camera,bluetooth,local_storage,autoplay_audible,autoplay_inaudible,media_key_system_access,saved_at) " + + "VALUES " + + "('${url.tryGetHostFromUrl()}',1,1,1,1,1,1,-1,-1,1,1)", + ) // Block audio and video. + } + + val dbVersion6 = + helper.runMigrationsAndValidate(MIGRATION_TEST_DB, 7, true, Migrations.migration_6_7) + + dbVersion6.query("SELECT * FROM site_permissions").use { cursor -> + cursor.moveToFirst() + val audible = cursor.getInt(cursor.getColumnIndexOrThrow("autoplay_audible")) + val inaudible = cursor.getInt(cursor.getColumnIndexOrThrow("autoplay_inaudible")) + assertEquals(-1, audible) // Block audio. + assertEquals(1, inaudible) // Allow inaudible. + } + } +} diff --git a/mobile/android/android-components/components/feature/sitepermissions/src/androidTest/java/mozilla/components/feature/sitepermissions/db/SitePermissionsDaoTest.kt b/mobile/android/android-components/components/feature/sitepermissions/src/androidTest/java/mozilla/components/feature/sitepermissions/db/SitePermissionsDaoTest.kt new file mode 100644 index 0000000000..60eba935dd --- /dev/null +++ b/mobile/android/android-components/components/feature/sitepermissions/src/androidTest/java/mozilla/components/feature/sitepermissions/db/SitePermissionsDaoTest.kt @@ -0,0 +1,112 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.sitepermissions.db + +import android.content.Context +import androidx.core.net.toUri +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import mozilla.components.concept.engine.permission.SitePermissions +import mozilla.components.concept.engine.permission.SitePermissions.AutoplayStatus +import mozilla.components.concept.engine.permission.SitePermissions.Status.ALLOWED +import mozilla.components.concept.engine.permission.SitePermissions.Status.BLOCKED +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +class SitePermissionsDaoTest { + + private val context: Context + get() = ApplicationProvider.getApplicationContext() + + private lateinit var database: SitePermissionsDatabase + private lateinit var dao: SitePermissionsDao + + @Before + fun setUp() { + database = Room.inMemoryDatabaseBuilder(context, SitePermissionsDatabase::class.java).build() + dao = database.sitePermissionsDao() + } + + @After + fun tearDown() { + database.close() + } + + @Test + fun testInsertingAndReadingSitePermissions() { + val origin = insertMockSitePermissions("https://www.mozilla.org") + + val siteFromDb = dao.getSitePermissionsBy(origin)!!.toSitePermission() + + assertEquals(origin, siteFromDb.origin) + assertEquals(BLOCKED, siteFromDb.camera) + assertEquals(AutoplayStatus.BLOCKED, siteFromDb.autoplayAudible) + assertEquals(AutoplayStatus.ALLOWED, siteFromDb.autoplayInaudible) + } + + @Test + fun testRemoveAllSitePermissions() { + for (index in 1..4) { + val origin = insertMockSitePermissions("https://www.mozilla$index.org") + + val sitePermissionFromDb = dao.getSitePermissionsBy(origin) + + assertEquals(origin, sitePermissionFromDb!!.origin) + } + + dao.deleteAllSitePermissions() + + val isEmpty = dao.getSitePermissions().isEmpty() + + assertTrue(isEmpty) + } + + @Test + fun testUpdateAndDeleteSitePermissions() { + val origin = insertMockSitePermissions("https://www.mozilla.org") + var siteFromDb = dao.getSitePermissionsBy(origin)!!.toSitePermission() + + assertEquals(BLOCKED, siteFromDb.camera) + assertEquals(AutoplayStatus.BLOCKED, siteFromDb.autoplayAudible) + assertEquals(AutoplayStatus.ALLOWED, siteFromDb.autoplayInaudible) + + dao.update( + siteFromDb.copy( + camera = ALLOWED, + autoplayInaudible = AutoplayStatus.ALLOWED, + autoplayAudible = AutoplayStatus.ALLOWED, + ).toSitePermissionsEntity(), + ) + + siteFromDb = dao.getSitePermissionsBy(origin)!!.toSitePermission() + + assertEquals(ALLOWED, siteFromDb.camera) + assertEquals(AutoplayStatus.ALLOWED, siteFromDb.autoplayAudible) + assertEquals(AutoplayStatus.ALLOWED, siteFromDb.autoplayInaudible) + + dao.deleteSitePermissions(siteFromDb.toSitePermissionsEntity()) + + val notFoundSitePermissions = dao.getSitePermissionsBy(origin)?.toSitePermission() + + assertNull(notFoundSitePermissions) + } + + private fun insertMockSitePermissions(url: String): String { + val origin = url.toUri().host!! + val sitePermissions = SitePermissions( + origin = origin, + camera = BLOCKED, + savedAt = System.currentTimeMillis(), + ) + dao.insert( + sitePermissions.toSitePermissionsEntity(), + ) + return origin + } +} diff --git a/mobile/android/android-components/components/feature/sitepermissions/src/main/AndroidManifest.xml b/mobile/android/android-components/components/feature/sitepermissions/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..e16cda1d34 --- /dev/null +++ b/mobile/android/android-components/components/feature/sitepermissions/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + diff --git a/mobile/android/android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/OnDiskSitePermissionsStorage.kt b/mobile/android/android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/OnDiskSitePermissionsStorage.kt new file mode 100644 index 0000000000..9941761d75 --- /dev/null +++ b/mobile/android/android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/OnDiskSitePermissionsStorage.kt @@ -0,0 +1,194 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.sitepermissions + +import android.content.Context +import androidx.annotation.VisibleForTesting +import androidx.paging.DataSource +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.launch +import mozilla.components.concept.engine.DataCleanable +import mozilla.components.concept.engine.Engine +import mozilla.components.concept.engine.Engine.BrowsingData.Companion.PERMISSIONS +import mozilla.components.concept.engine.permission.PermissionRequest +import mozilla.components.concept.engine.permission.SitePermissions +import mozilla.components.concept.engine.permission.SitePermissions.Status +import mozilla.components.concept.engine.permission.SitePermissions.Status.ALLOWED +import mozilla.components.concept.engine.permission.SitePermissionsStorage +import mozilla.components.concept.engine.permission.SitePermissionsStorage.Permission +import mozilla.components.concept.engine.permission.SitePermissionsStorage.Permission.AUTOPLAY_AUDIBLE +import mozilla.components.concept.engine.permission.SitePermissionsStorage.Permission.AUTOPLAY_INAUDIBLE +import mozilla.components.concept.engine.permission.SitePermissionsStorage.Permission.BLUETOOTH +import mozilla.components.concept.engine.permission.SitePermissionsStorage.Permission.CAMERA +import mozilla.components.concept.engine.permission.SitePermissionsStorage.Permission.LOCAL_STORAGE +import mozilla.components.concept.engine.permission.SitePermissionsStorage.Permission.LOCATION +import mozilla.components.concept.engine.permission.SitePermissionsStorage.Permission.MEDIA_KEY_SYSTEM_ACCESS +import mozilla.components.concept.engine.permission.SitePermissionsStorage.Permission.MICROPHONE +import mozilla.components.concept.engine.permission.SitePermissionsStorage.Permission.NOTIFICATION +import mozilla.components.concept.engine.permission.SitePermissionsStorage.Permission.STORAGE_ACCESS +import mozilla.components.feature.sitepermissions.db.SitePermissionsDatabase +import mozilla.components.feature.sitepermissions.db.toSitePermissionsEntity + +/** + * A storage implementation to save [SitePermissions] on disk. + */ +class OnDiskSitePermissionsStorage( + context: Context, + private val dataCleanable: DataCleanable? = null, +) : SitePermissionsStorage { + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal var databaseInitializer = { + SitePermissionsDatabase.get(context) + } + + private val coroutineScope = CoroutineScope(Main) + private val database by lazy { databaseInitializer() } + + /** + * Persists the [sitePermissions] provided as a parameter. + * @param sitePermissions the [sitePermissions] to be stored. + * @param private indicates if the [SitePermissions] belongs to a private session. + */ + override suspend fun save( + sitePermissions: SitePermissions, + request: PermissionRequest?, + private: Boolean, + ) { + if (private) return // never save private browsing site permissions + database + .sitePermissionsDao() + .insert( + sitePermissions.toSitePermissionsEntity(), + ) + } + + /** + * Replaces an existing SitePermissions with the values of [sitePermissions] provided as a parameter. + * @param sitePermissions the sitePermissions to be updated. + * @param private indicates if the [SitePermissions] belongs to a private session. + */ + override suspend fun update(sitePermissions: SitePermissions, private: Boolean) { + if (private) return + coroutineScope.launch { + dataCleanable?.clearData(Engine.BrowsingData.select(PERMISSIONS), sitePermissions.origin) + } + database.sitePermissionsDao() + .update(sitePermissions.toSitePermissionsEntity()) + } + + /** + * Finds all SitePermissions that match the [origin]. + * @param origin the site to be used as filter in the search. + * @param private indicates if the [SitePermissions] belongs to a private session. + */ + override suspend fun findSitePermissionsBy( + origin: String, + includeTemporary: Boolean, + private: Boolean, + ): SitePermissions? { + if (private) return null + return database + .sitePermissionsDao() + .getSitePermissionsBy(origin) + ?.toSitePermission() + } + + /** + * Returns all saved [SitePermissions] instances as a [DataSource.Factory]. + * + * A consuming app can transform the data source into a `LiveData` of when using RxJava2 into a + * `Flowable` or `Observable`, that can be observed. + * + * - https://developer.android.com/topic/libraries/architecture/paging/data + * - https://developer.android.com/topic/libraries/architecture/paging/ui + */ + override suspend fun getSitePermissionsPaged(): DataSource.Factory { + return database + .sitePermissionsDao() + .getSitePermissionsPaged() + .map { entity -> + entity.toSitePermission() + } + } + + /** + * Finds all SitePermissions grouped by [Permission]. + * @return a map of site grouped by [Permission]. + */ + suspend fun findAllSitePermissionsGroupedByPermission(): Map> { + val sitePermissions = all() + val map = mutableMapOf>() + + sitePermissions.forEach { permission -> + with(permission) { + map.putIfAllowed(BLUETOOTH, bluetooth, permission) + map.putIfAllowed(MICROPHONE, microphone, permission) + map.putIfAllowed(CAMERA, camera, permission) + map.putIfAllowed(LOCAL_STORAGE, localStorage, permission) + map.putIfAllowed(NOTIFICATION, notification, permission) + map.putIfAllowed(LOCATION, location, permission) + map.putIfAllowed(AUTOPLAY_AUDIBLE, autoplayAudible.toStatus(), permission) + map.putIfAllowed(AUTOPLAY_INAUDIBLE, autoplayInaudible.toStatus(), permission) + map.putIfAllowed(MEDIA_KEY_SYSTEM_ACCESS, mediaKeySystemAccess, permission) + map.putIfAllowed(STORAGE_ACCESS, crossOriginStorageAccess, permission) + } + } + return map + } + + /** + * Deletes all sitePermissions that match the sitePermissions provided as a parameter. + * @param sitePermissions the sitePermissions to be deleted from the storage. + */ + override suspend fun remove(sitePermissions: SitePermissions, private: Boolean) { + coroutineScope.launch { + dataCleanable?.clearData(Engine.BrowsingData.select(PERMISSIONS), sitePermissions.origin) + } + database + .sitePermissionsDao() + .deleteSitePermissions( + sitePermissions.toSitePermissionsEntity(), + ) + } + + /** + * Deletes all sitePermissions sitePermissions. + */ + override suspend fun removeAll() { + coroutineScope.launch { + dataCleanable?.clearData(Engine.BrowsingData.select(PERMISSIONS)) + } + return database + .sitePermissionsDao() + .deleteAllSitePermissions() + } + + /** + * Returns all sitePermissions in the store. + */ + override suspend fun all(): List { + return database + .sitePermissionsDao() + .getSitePermissions() + .map { + it.toSitePermission() + } + } + + private fun MutableMap>.putIfAllowed( + permission: Permission, + status: Status, + sitePermissions: SitePermissions, + ) { + if (status == ALLOWED) { + if (this.containsKey(permission)) { + this[permission]?.add(sitePermissions) + } else { + this[permission] = mutableListOf(sitePermissions) + } + } + } +} diff --git a/mobile/android/android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/SitePermissionsDialogFragment.kt b/mobile/android/android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/SitePermissionsDialogFragment.kt new file mode 100644 index 0000000000..61a6b10d8b --- /dev/null +++ b/mobile/android/android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/SitePermissionsDialogFragment.kt @@ -0,0 +1,258 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.sitepermissions + +import android.annotation.SuppressLint +import android.app.Dialog +import android.content.DialogInterface +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.View.VISIBLE +import android.view.ViewGroup +import android.view.Window +import android.widget.Button +import android.widget.CheckBox +import android.widget.ImageView +import android.widget.LinearLayout.LayoutParams +import android.widget.TextView +import androidx.appcompat.app.AppCompatDialogFragment +import androidx.core.content.ContextCompat + +internal const val KEY_SESSION_ID = "KEY_SESSION_ID" +internal const val KEY_TITLE = "KEY_TITLE" +private const val KEY_DIALOG_GRAVITY = "KEY_DIALOG_GRAVITY" +private const val KEY_DIALOG_WIDTH_MATCH_PARENT = "KEY_DIALOG_WIDTH_MATCH_PARENT" +private const val KEY_TITLE_ICON = "KEY_TITLE_ICON" +private const val KEY_MESSAGE = "KEY_MESSAGE" +private const val KEY_NEGATIVE_BUTTON_TEXT = "KEY_NEGATIVE_BUTTON_TEXT" +private const val KEY_POSITIVE_BUTTON_BACKGROUND_COLOR = "KEY_POSITIVE_BUTTON_BACKGROUND_COLOR" +private const val KEY_POSITIVE_BUTTON_TEXT_COLOR = "KEY_POSITIVE_BUTTON_TEXT_COLOR" +private const val KEY_SHOULD_SHOW_LEARN_MORE_LINK = "KEY_SHOULD_SHOW_LEARN_MORE_LINK" +private const val KEY_SHOULD_SHOW_DO_NOT_ASK_AGAIN_CHECKBOX = "KEY_SHOULD_SHOW_DO_NOT_ASK_AGAIN_CHECKBOX" +private const val KEY_SHOULD_PRESELECT_DO_NOT_ASK_AGAIN_CHECKBOX = "KEY_SHOULD_PRESELECT_DO_NOT_ASK_AGAIN_CHECKBOX" +private const val KEY_IS_NOTIFICATION_REQUEST = "KEY_IS_NOTIFICATION_REQUEST" +private const val DEFAULT_VALUE = Int.MAX_VALUE +private const val KEY_PERMISSION_ID = "KEY_PERMISSION_ID" + +internal open class SitePermissionsDialogFragment : AppCompatDialogFragment() { + + // Safe Arguments + + private val safeArguments get() = requireNotNull(arguments) + + internal val sessionId: String get() = + safeArguments.getString(KEY_SESSION_ID, "") + internal val title: String get() = + safeArguments.getString(KEY_TITLE, "") + internal val icon get() = + safeArguments.getInt(KEY_TITLE_ICON, DEFAULT_VALUE) + internal val message: String? get() = + safeArguments.getString(KEY_MESSAGE, null) + internal val negativeButtonText: String? get() = + safeArguments.getString(KEY_NEGATIVE_BUTTON_TEXT, null) + + internal val dialogGravity: Int get() = + safeArguments.getInt(KEY_DIALOG_GRAVITY, DEFAULT_VALUE) + internal val dialogShouldWidthMatchParent: Boolean get() = + safeArguments.getBoolean(KEY_DIALOG_WIDTH_MATCH_PARENT) + + internal val positiveButtonBackgroundColor get() = + safeArguments.getInt(KEY_POSITIVE_BUTTON_BACKGROUND_COLOR, DEFAULT_VALUE) + internal val positiveButtonTextColor get() = + safeArguments.getInt(KEY_POSITIVE_BUTTON_TEXT_COLOR, DEFAULT_VALUE) + + internal val isNotificationRequest get() = + safeArguments.getBoolean(KEY_IS_NOTIFICATION_REQUEST, false) + + internal val shouldShowLearnMoreLink: Boolean get() = + safeArguments.getBoolean(KEY_SHOULD_SHOW_LEARN_MORE_LINK, false) + internal val shouldShowDoNotAskAgainCheckBox: Boolean get() = + safeArguments.getBoolean(KEY_SHOULD_SHOW_DO_NOT_ASK_AGAIN_CHECKBOX, true) + internal val shouldPreselectDoNotAskAgainCheckBox: Boolean get() = + safeArguments.getBoolean(KEY_SHOULD_PRESELECT_DO_NOT_ASK_AGAIN_CHECKBOX, false) + internal val permissionRequestId: String get() = + safeArguments.getString(KEY_PERMISSION_ID, "") + + // State + + internal var feature: SitePermissionsFeature? = null + internal var userSelectionCheckBox: Boolean = false + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + userSelectionCheckBox = shouldPreselectDoNotAskAgainCheckBox + + val sheetDialog = Dialog(requireContext()) + sheetDialog.requestWindowFeature(Window.FEATURE_NO_TITLE) + sheetDialog.setCanceledOnTouchOutside(true) + + val rootView = createContainer() + + sheetDialog.setContainerView(rootView) + + sheetDialog.window?.apply { + if (dialogGravity != DEFAULT_VALUE) { + setGravity(dialogGravity) + } + + if (dialogShouldWidthMatchParent) { + setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + // This must be called after addContentView, or it won't fully fill to the edge. + setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + } + } + + return sheetDialog + } + + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + feature?.onDismiss(permissionRequestId, sessionId) + } + + private fun Dialog.setContainerView(rootView: View) { + if (dialogShouldWidthMatchParent) { + setContentView(rootView) + } else { + addContentView( + rootView, + LayoutParams( + LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT, + ), + ) + } + } + + @SuppressLint("InflateParams") + private fun createContainer(): View { + val rootView = LayoutInflater.from(requireContext()).inflate( + R.layout.mozac_site_permissions_prompt, + null, + false, + ) + + rootView.findViewById(R.id.title).text = title + rootView.findViewById(R.id.icon).setImageResource(icon) + message?.let { + rootView.findViewById(R.id.message).apply { + visibility = VISIBLE + text = it + } + } + if (shouldShowLearnMoreLink) { + rootView.findViewById(R.id.learn_more).apply { + visibility = VISIBLE + isLongClickable = false + setOnClickListener { + dismiss() + feature?.onLearnMorePress(permissionRequestId, sessionId) + } + } + } + + val positiveButton = rootView.findViewById