Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RuntimePermissionsExtended

 RuntimePermissionsExtended

Kotlin Serbia meetup #4

Talk about Kotlin and its usage in Android Runtime permission handling.

Nebojša Vukšić

June 19, 2017
Tweet

More Decks by Nebojša Vukšić

Other Decks in Programming

Transcript

  1. Android permissions definition • part of Android security system •

    used to control access to system features, data, etc. • apps are in sandbox • needed permission needs to be annotated in AndroidManifest.xml <uses-permission android:name="android.permission.CAMERA" />
  2. Pre-Marshmallow • targetSdkVersion < 23 • all permissions are granted

    in install time • less control for user • no explanation for permission usage • when app is updated we need to check additional permissions
  3. After Marshmallow • targetSdkVersion >= 23 • permissions are requested

    in runtime • all edge cases need to be covered • User can revoke permission from settings screen • Process is revoked
  4. After Marshmallow • targetSdkVersion >= 23 • permissions are requested

    in runtime • all edge cases need to be covered • User can revoke permission from settings screen • Process is revoked
  5. Permission protection level • Normal permissions • Dangerous permissions •

    Signature permissions • Signature and system permissions
  6. Permission protection level • Normal permissions • Dangerous permissions •

    Signature permissions • Signature and system permissions
  7. Normal permissions • granted when app is installed • they

    are not risk for users data privacy and can’t affect other apps Permission list: • ACCESS_NETWORK_STATE • BLUETOOTH • INTERNET • NFC • VIBRATE • WAKE_LOCK • etc...
  8. Permission protection level • Normal permissions • Dangerous permissions •

    Signature permissions • Signature and system permissions
  9. Permission protection level • Normal permissions • Dangerous permissions •

    Signature permissions • Signature and system permissions
  10. Dangerous permissions • requested in runtime • when permission from

    one group is granted, all permissions in group are granted • they are risk for data privacy and can affect other apps
  11. Pre-Marshmallow - targetSdk < 23 import kotlinx.android.synthetic.main.activity_camera.* class CameraActivity :

    AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) setSupportActionBar(toolbar) //Start camera preview startCameraPreviewScreen() } }
  12. After Marshmallow - targetSdk >= 23 class CameraActivity : AppCompatActivity()

    { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) setSupportActionBar(toolbar) //Start camera preview if (PermissionChecker.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { startCameraPreviewScreen() } } }
  13. After Marshmallow - targetSdk >= 23 class CameraActivity : AppCompatActivity()

    { private val REQUEST_CAMERA_PERMISSION: Int = 1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) setSupportActionBar(toolbar) //Start camera preview if (PermissionChecker.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { startCameraPreviewScreen() } else { ActivityCompat.requestPermissions(this, arrayOf<String>(android.Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION) } } }
  14. After Marshmallow - targetSdk >= 23 override fun onRequestPermissionsResult(requestCode: Int,

    permissions: Array<out String>, grantResults: IntArray) { when (requestCode) { REQUEST_CAMERA_PERMISSION -> { if (grantResults.filterNot { it == PackageManager.PERMISSION_GRANTED }.isEmpty()) { startCameraPreviewScreen() } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.CAMERA)) { showSnackBarWithExplanation() } else { startGoToSettingsScreenIntent() } } } } }
  15. After Marshmallow - targetSdk >= 23 class CameraActivity : AppCompatActivity()

    { private val REQUEST_CAMERA_PERMISSION: Int = 1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) setSupportActionBar(toolbar) //Start camera preview if (PermissionChecker.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { startCameraPreviewScreen() } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.CAMERA)) { showSnackBarWithExplanation() } else { ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION) } } } }
  16. RuntimePermissionExtended class CameraActivity : AppCompatActivity() { private val REQUEST_CAMERA_PERMISSION: Int

    = 1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) setSupportActionBar(toolbar) //Start camera preview if (PermissionChecker.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { startCameraPreviewScreen() } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.CAMERA)) { showSnackBarWithExplanation() } else { ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION) } } } } OnPermissionGranted Block OnExplanationNeeded Block OnPermissionDenied Block
  17. RuntimePermissionExtended inline fun handlePermission(onGranted: () -> Unit, onExplanationNeeded: () ->

    Unit, onDenied: () -> Unit) { if (PermissionChecker.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { onGranted() } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.CAMERA)) { onExplanationNeeded() } else { onDenied() } } } inline modifier - inlines onGranted, onDenied an onExplanationNeeded functions on call site
  18. RuntimePermissionExtended class CameraActivity : AppCompatActivity() { private val REQUEST_CAMERA_PERMISSION: Int

    = 1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) setSupportActionBar(toolbar) //Start camera preview handlePermission( onGranted = { startCameraPreviewScreen() }, onExplanationNeeded = { //Show explanation }, onDenied = { ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION) } ) } }
  19. RuntimePermissionExtended inline fun handlePermission(permission: AppPermission, onGranted: () -> Unit, onExplanationNeeded:

    () -> Unit, onDenied: () -> Unit) { if (PermissionChecker.checkSelfPermission(this, permission.permissionName) == PackageManager.PERMISSION_GRANTED) { onGranted() } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission.permissionName)) { onExplanationNeeded() } else { onDenied() } } } sealed class AppPermission(val permissionName: String, val requestCode: Int, val deniedMessageId: Int, val explanationMessageId: Int) { companion object { val permissions: List<AppPermission> by lazy { listOf(CAMERA) } } object CAMERA: AppPermission(permissionName = Manifest.permission.CAMERA, requestCode = 1, deniedMessageId = R.string.permission_camera_denied, explanationMessageId = R.string.permission_camera_explanation) }
  20. RuntimePermissionExtended class CameraActivity : AppCompatActivity() { private val REQUEST_CAMERA_PERMISSION: Int

    = 1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) setSupportActionBar(toolbar) //Start camera preview handlePermission(AppPermission.CAMERA, onGranted = { startCameraPreviewScreen() }, onExplanationNeeded = { //Show explanation }, onDenied = { ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION) } ) } } No information about requested permission
  21. RuntimePermissionExtended inline fun handlePermission(permission: AppPermission, onGranted: () -> Unit, onExplanationNeeded:

    (AppPermission) -> Unit, onDenied: (AppPermission) -> Unit) { if (PermissionChecker.checkSelfPermission(this, permission.permissionName) == PackageManager.PERMISSION_GRANTED) { onGranted() } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission.permissionName)) { onExplanationNeeded(permission) } else { onDenied(permission) } } } sealed class AppPermission(val permissionName: String, val requestCode: Int, val deniedMessageId: Int, val explanationMessageId: Int) { companion object { val permissions: List<AppPermission> by lazy { listOf(CAMERA) } } object CAMERA: AppPermission(permissionName = Manifest.permission.CAMERA, requestCode = 1, deniedMessageId = R.string.permission_camera_denied, explanationMessageId = R.string.permission_camera_explanation) }
  22. RuntimePermissionExtended class CameraActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) setSupportActionBar(toolbar) //Start camera preview handlePermission(AppPermission.CAMERA, onGranted = { startCameraPreviewScreen() }, onExplanationNeeded = { permission -> showSnackBarWithExplanation(permission.explanationMessageId) }, onDenied = { permission -> ActivityCompat.requestPermissions(this, arrayOf(permission.permissionName), permission.requestCode) } ) } }
  23. RuntimePermissionExtended class CameraActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) setSupportActionBar(toolbar) //Start camera preview handlePermission(AppPermission.CAMERA, onGranted = { startCameraPreviewScreen() }, onExplanationNeeded = { showSnackBarWithExplanation(it.explanationMessageId) }, onDenied = { ActivityCompat.requestPermissions(this, arrayOf(it.permissionName), it.requestCode) } ) } }
  24. RuntimePermissionExtended inline fun handlePermission(permission: AppPermission, onGranted: () -> Unit, onExplanationNeeded:

    (AppPermission) -> Unit, onDenied: (AppPermission) -> Unit) { if (PermissionChecker.checkSelfPermission(this, permission.permissionName) == PackageManager.PERMISSION_GRANTED) { onGranted() } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission.permissionName)) { onExplanationNeeded(permission) } else { onDenied(permission) } } }
  25. RuntimePermissionExtended inline fun Activity.handlePermission(permission: AppPermission, onGranted: () -> Unit, onExplanationNeeded:

    (AppPermission) -> Unit, onDenied: (AppPermission) -> Unit) { if (PermissionChecker.checkSelfPermission(this, permission.permissionName) == PackageManager.PERMISSION_GRANTED) { onGranted() } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission.permissionName)) { onExplanationNeeded(permission) } else { onDenied(permission) } } } fun Activity.isPermissionGranted(permission: AppPermission) = (PermissionChecker.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) fun Activity.isExplanationNeeded(permission: AppPermission) = ActivityCompat.shouldShowRequestPermissionRationale(this, permission.permissionName)
  26. RuntimePermissionExtended inline fun Activity.handlePermission(permission: AppPermission, onGranted: () -> Unit, onExplanationNeeded:

    (AppPermission) -> Unit, onDenied: (AppPermission) -> Unit) { if (isPermissionGranted(this, permission.permissionName) == PackageManager.PERMISSION_GRANTED) { onGranted() } else { if (isExplanationNeeded(this, permission.permissionName)) { onExplanationNeeded(permission) } else { onDenied(permission) } } } fun Activity.isPermissionGranted(permission: AppPermission) = (PermissionChecker.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) fun Activity.isExplanationNeeded(permission: AppPermission) = ActivityCompat.shouldShowRequestPermissionRationale(this, permission.permissionName)
  27. RuntimePermissionExtended inline fun Activity.handlePermission(permission: AppPermission, onGranted: () -> Unit, onExplanationNeeded:

    (AppPermission) -> Unit, onDenied: (AppPermission) -> Unit) { if (isPermissionGranted(this, permission.permissionName) == PackageManager.PERMISSION_GRANTED) { onGranted() } else { if (isExplanationNeeded(this, permission.permissionName)) { onExplanationNeeded(permission) } else { onDenied(permission) } } } fun Activity.isPermissionGranted(permission: AppPermission) = (PermissionChecker.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) fun Activity.isExplanationNeeded(permission: AppPermission) = ActivityCompat.shouldShowRequestPermissionRationale(this, permission.permissionName)
  28. RuntimePermissionExtended inline fun Activity.handlePermission(permission: AppPermission, onGranted: () -> Unit, onExplanationNeeded:

    (AppPermission) -> Unit, onDenied: (AppPermission) -> Unit) { when { isPermissionGranted(permission) -> onGranted() isExplanationNeeded(permission) -> onExplanationNeeded(permission) else -> onDenied(permission) } } fun Activity.isPermissionGranted(permission: AppPermission) = (PermissionChecker.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) fun Activity.isExplanationNeeded(permission: AppPermission) = ActivityCompat.shouldShowRequestPermissionRationale(this, permission.permissionName)
  29. RuntimePermissionExtended class CameraActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) setSupportActionBar(toolbar) //Start camera preview handlePermission(AppPermission.CAMERA, onGranted = { startCameraPreviewScreen() }, onExplanationNeeded = { showSnackBarWithExplanation(it.explanationMessageId) }, onDenied = { ActivityCompat.requestPermissions(this, arrayOf(it.permissionName), it.requestCode) } ) } } fun Activity.requestPermission(permission: AppPermission) = ActivityCompat.requestPermissions(this, arrayOf(permission.permissionName), permission.requestCode)
  30. RuntimePermissionExtended class CameraActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) setSupportActionBar(toolbar) //Start camera preview handlePermission(AppPermission.CAMERA, onGranted = { startCameraPreviewScreen() }, onExplanationNeeded = { showSnackBarWithExplanation(it.explanationMessageId) }, onDenied = { requestPermission(it) } ) } } fun Activity.requestPermission(permission: AppPermission) = ActivityCompat.requestPermissions(this, arrayOf(permission.permissionName), permission.requestCode)
  31. override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {

    when (requestCode) { REQUEST_CAMERA_PERMISSION -> { if (grantResults.filterNot { it == PackageManager.PERMISSION_GRANTED }.isEmpty()) { startCameraPreviewScreen() } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.CAMERA)) { showSnackBarWithExplanation() } else { startGoToSettingsIntent() } } } } } RuntimePermissionExtended
  32. fun Activity.onPermissionRequestResultReceived(requestCode: Int, grantResults: IntArray, onGranted: () -> Unit, onExplanationNeeded:

    (AppPermission) -> Unit, onDenied: (AppPermission) -> Unit) { AppPermission.permissions.find { requestCode == it.requestCode }?.let { when { arePermissionsGranted(grantResults) -> onGranted() isExplanationNeeded(it) -> onExplanationNeeded(it) else -> onDenied(it) } } } private fun arePermissionsGranted(grantResults: IntArray) = grantResults.filterNot { it == PackageManager.PERMISSION_GRANTED }.isEmpty() RuntimePermissionExtended
  33. override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {

    onPermissionRequestResultReceived(requestCode, grantResults, onGranted = { startCameraPreviewScreen() }, onExplanationNeeded = { showSnackBarWithExplanation(it.explanationMessageId) }, onDenied = { startGoToSettingsScreenIntent() } ) } RuntimePermissionExtended
  34. Overview - before and now class CameraActivity : AppCompatActivity() {

    private val REQUEST_CAMERA_PERMISSION: Int = 1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) setSupportActionBar(toolbar) //Start camera preview if (PermissionChecker.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { startCameraPreviewScreen() } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.CAMERA)) { showSnackBarWithExplanation() } else { ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION) } } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { when (requestCode) { REQUEST_CAMERA_PERMISSION -> { if (grantResults.filterNot { it == PackageManager.PERMISSION_GRANTED }.isEmpty()) { showCameraPreviewScreen() } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.CAMERA)){ showSnackBarWithExplanation() } else { startGoToSettingsScreenIntent() } } } } } } class CameraActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) setSupportActionBar(toolbar) handlePermission(AppPermission.CAMERA, onGranted = { startCameraPreviewScreen() }, onExplanationNeeded = { showSnackbarExplanation }, onDenied = { requestPermission(it) } ) } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { onPermissionRequestResultReceived(requestCode, grantResults, onGranted = { startCameraPreviewScreen() }, onExplanationNeeded = { showSnackbarExplanation() }, onDenied = { startGoToSettingsScreenIntent() } ) } } Before Now
  35. Summary • use PermissionChecker • extension functions • higher order

    functions • use combination of two concepts to reduce amount of boilerplate code • use inline modifier
  36. Resources • RuntimePermissionsExtended • Official Kotlin documentation • Official Kotlin

    Github • Kotlin koans • Awesome Kotlin – collection of materials • Slack kanal • #droidconpl2015 - Shintaro Katafuchi 'Managing Runtime Permissions' • Sonja Kesić from Endava: May I? - droidcon Zagreb 2016 • Runtime Permissions - official Android documentation • PermissionsDispatcher library
  37. Nebojša Vukšić Android developer @ codecentric Founder of Kotlin User

    Group Serbia [email protected] Kotlin User Group Serbia https://www.meetup.com/Serbia-Kotlin-User-Group https://www.facebook.com/kotlinserbia/ https://twitter.com/kotlin_serbia TheTechW0lf