taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

ConfigCameraPermissionTest.kt (6198B)


      1 package net.taler.merchantpos.config
      2 
      3 import android.Manifest
      4 import android.app.Application
      5 import android.content.Context
      6 import android.content.pm.PackageManager
      7 import android.content.res.Configuration
      8 import android.os.Bundle
      9 import android.os.Looper
     10 import android.provider.Settings
     11 import android.widget.FrameLayout
     12 
     13 import androidx.fragment.app.FragmentActivity
     14 import androidx.lifecycle.ViewModelProvider
     15 import androidx.test.core.app.ApplicationProvider
     16 
     17 import net.taler.merchantpos.R
     18 
     19 import org.junit.After
     20 import org.junit.Assert.assertEquals
     21 import org.junit.Assert.assertNotNull
     22 import org.junit.Assert.assertNotSame
     23 import org.junit.Assert.assertNull
     24 import org.junit.Assert.assertSame
     25 import org.junit.Before
     26 import org.junit.Test
     27 import org.junit.runner.RunWith
     28 import org.robolectric.Robolectric
     29 import org.robolectric.RobolectricTestRunner
     30 import org.robolectric.Shadows.shadowOf
     31 import org.robolectric.android.controller.ActivityController
     32 import org.robolectric.annotation.Config
     33 import org.robolectric.shadows.ShadowChoreographer
     34 import org.robolectric.shadows.ShadowToast
     35 
     36 @RunWith(RobolectricTestRunner::class)
     37 @Config(sdk = [28], application = Application::class)
     38 class ConfigCameraPermissionTest
     39 {
     40     private lateinit var controller: ActivityController<CameraTestActivity>
     41     private val fragment: ConfigFragment
     42         get() = controller.get().supportFragmentManager.findFragmentByTag("config") as ConfigFragment
     43 
     44     @Before
     45     fun setUp()
     46     {
     47         val app = ApplicationProvider.getApplicationContext<Application>()
     48         shadowOf(app).grantPermissions("${app.packageName}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION")
     49         shadowOf(app).denyPermissions(Manifest.permission.CAMERA)
     50         app
     51             .getSharedPreferences("config.cameraPermission", Context.MODE_PRIVATE)
     52             .edit()
     53             .clear()
     54             .commit()
     55         ShadowChoreographer.setPaused(true)
     56         controller = Robolectric.buildActivity(CameraTestActivity::class.java).setup()
     57         ViewModelProvider(controller.get())[ConfigDraftViewModel::class.java].apply {
     58             initialized = true
     59             mode = ConfigMode.Qr
     60             merchantUrl = "example.invalid"
     61         }
     62         controller
     63             .get()
     64             .supportFragmentManager
     65             .beginTransaction()
     66             .add(android.R.id.content, ConfigFragment(), "config")
     67             .commitNow()
     68         idle()
     69     }
     70 
     71     @After
     72     fun tearDown()
     73     {
     74         controller.pause().stop().destroy()
     75         idle()
     76     }
     77 
     78     @Test
     79     fun `entering and resuming QR setup do not automatically request permission or toast`()
     80     {
     81         repeat(3) {
     82             controller.pause().resume()
     83             idle()
     84         }
     85         assertNull(shadowOf(controller.get()).lastRequestedPermission)
     86         assertEquals(0, ShadowToast.shownToastCount())
     87     }
     88 
     89     @Test
     90     fun `only confirmation launches permission and repeated taps cannot launch duplicates`()
     91     {
     92         confirm()
     93         val request = shadowOf(controller.get()).lastRequestedPermission
     94         assertNotNull(request)
     95         assertEquals(listOf(Manifest.permission.CAMERA), request.requestedPermissions.toList())
     96 
     97         confirm()
     98         assertSame(request, shadowOf(controller.get()).lastRequestedPermission)
     99     }
    100 
    101     @Test
    102     fun `two denials stop requesting and offer app settings without toasts`()
    103     {
    104         confirm()
    105         deny(canAskAgain = true)
    106         val first = shadowOf(controller.get()).lastRequestedPermission
    107         controller.pause().resume()
    108         idle()
    109         assertSame(first, shadowOf(controller.get()).lastRequestedPermission)
    110 
    111         confirm()
    112         val second = shadowOf(controller.get()).lastRequestedPermission
    113         assertNotSame(first, second)
    114         deny(canAskAgain = false)
    115 
    116         repeat(3) {
    117             controller.pause().resume()
    118             idle()
    119         }
    120         assertSame(second, shadowOf(controller.get()).lastRequestedPermission)
    121         assertEquals(0, ShadowToast.shownToastCount())
    122 
    123         confirm()
    124         val settings = shadowOf(controller.get()).nextStartedActivity
    125         assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, settings.action)
    126         assertEquals("package:${controller.get().packageName}", settings.dataString)
    127         assertSame(second, shadowOf(controller.get()).lastRequestedPermission)
    128     }
    129 
    130     @Test
    131     fun `blocked permission still opens settings after rotation`()
    132     {
    133         confirm()
    134         deny(canAskAgain = false)
    135         controller.configurationChange(
    136             Configuration(controller.get().resources.configuration).apply {
    137                 orientation = Configuration.ORIENTATION_LANDSCAPE
    138             },
    139         )
    140         idle()
    141 
    142         confirm()
    143         assertEquals(
    144             Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
    145             shadowOf(controller.get()).nextStartedActivity.action
    146         )
    147         assertNull(shadowOf(controller.get()).lastRequestedPermission)
    148         assertEquals(0, ShadowToast.shownToastCount())
    149     }
    150 
    151     private fun confirm()
    152     {
    153         // Exercise the same action wired to the Compose dialog button without a camera backend.
    154         ConfigFragment::class.java
    155             .getDeclaredMethod("confirmCameraPermission")
    156             .apply {
    157                 isAccessible = true
    158             }.invoke(fragment)
    159         idle()
    160     }
    161 
    162     private fun deny(canAskAgain: Boolean)
    163     {
    164         shadowOf(controller.get().packageManager)
    165             .setShouldShowRequestPermissionRationale(Manifest.permission.CAMERA, canAskAgain)
    166         val request = shadowOf(controller.get()).lastRequestedPermission
    167         controller.get().onRequestPermissionsResult(
    168             request.requestCode,
    169             request.requestedPermissions,
    170             intArrayOf(PackageManager.PERMISSION_DENIED),
    171         )
    172         idle()
    173     }
    174 
    175     private fun idle() = shadowOf(Looper.getMainLooper()).idle()
    176 
    177     class CameraTestActivity : FragmentActivity()
    178     {
    179         override fun onCreate(savedInstanceState: Bundle?)
    180         {
    181             setTheme(R.style.AppTheme)
    182             super.onCreate(savedInstanceState)
    183             setContentView(FrameLayout(this))
    184         }
    185     }
    186 }