Skip to main content

10 - Android Permissions

Permissions

Permissions are needed for:

  • restricted data (system state)
  • restricted actions (recording audio)

NB! When you include a library, you also inherit its permission requirements. Be aware of the permissions that each dependency requires and what those permissions are used for.

All permissions are listed here:
https://developer.android.com/reference/android/Manifest.permission

Permissions are first declared in manifest.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />

<application... />

Permissions can be categorized into:

  • install-time
  • run time
  • special

General workflow

Workflow overview

Install-time permissions

Install-time permissions are presented in app store, and are automatically granted during app install.
Install time permissions are divided into normal and signature.

  • Normal - data and actions that extend past your app sandbox, but little risk.
  • Signature - app is signed by the same certificate as the app or the OS that defines the permission (autofill, vpn, etc).

Install permissions

Run time permissions

Also known as dangerous permissions (location, contact, microphone)...
You need to request runtime permissions in your app before you can access the restricted data or perform restricted actions. And these need to be checked before every restricted access.
When your app requests a runtime permission, the system presents a runtime permission prompt:
Runtime permissions

Special permissions

Only the platform and OEMs can define special permissions - usally to protect powerful actions, such as drawing over other apps.

Look at the Special app access page in system settings.

Special permissions

Camera, bluetooth, etc...
https://developer.android.com/guide/topics/manifest/uses-feature-element#permissions-features

Is hardware actually required? Specify in manifest. If you require hardware permission in manifest, system by default assumes, that this hardware is required.

<uses-feature android:name="android.hardware.camera"
android:required="false" />

Detect hardware during app run...

if (applicationContext.packageManager.hasSystemFeature(
PackageManager.FEATURE_CAMERA_FRONT)) {
// do something with camera
} else {
// alternative flow
}

Request runtime permissions

Workflow runtime

General steps

  • declare needed permissions in manifest
  • during runtime - check, maybe you already have the permission. done.
  • check, if rationale/explanation UI is needed (system decides). If so, display it, and wait for user acknowledgemnt
  • request the runtime permission
  • check response (granted or not)
  • if not granted - gracefully degrade

Declare in manifest

<manifest ...>

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />

https://developer.android.com/reference/android/Manifest.permission

Check for existing permission grant

Pass needed permission to ContextCompat.checkSelfPermission(). Returns PERMISSION_GRANTED or PERMISSION_DENIED.
If permission is granted - continue normally.

Check for rationale need

Users grant privileges much more readily when the need for them is explained well. Permissions dialog has no explanations, why your app needs the privileges.

If the ContextCompat.checkSelfPermission() method returns PERMISSION_DENIED, call shouldShowRequestPermissionRationale() - returns true or false. If true - show an educational UI.

Additionally, if your app requests a permission related to location, microphone, or camera, consider explaining why your app needs access to this information.

Add activity with following (for location, mic or camera):

<!-- android:exported required if you target Android 12. -->
<activity android:name=".DataAccessRationaleActivity"
android:permission="android.permission.START_VIEW_PERMISSION_USAGE"
android:exported="true">
<!-- VIEW_PERMISSION_USAGE shows a selectable information icon on
your app permission's page in system settings.
VIEW_PERMISSION_USAGE_FOR_PERIOD shows a selectable information
icon on the Privacy Dashboard screen. -->
<intent-filter>
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE_FOR_PERIOD" />
<category android:name="android.intent.category.DEFAULT" />
...
</intent-filter>
</activity>
  • If you add the intent filter that contains the VIEW_PERMISSION_USAGE action, users see the icon on your app's permissions page in system settings. You can apply this action to all runtime permissions.
  • If you add the intent filter that contains the VIEW_PERMISSION_USAGE_FOR_PERIOD action, users see the icon next to your app's name whenever your app appears in the Privacy Dashboard screen.

When users select that icon, your app's rationale activity is started.

Request permission

Use the RequestPermission contract in AndroidX. Allows the system to manage the permission request code. If needed you can also manage a request code yourself as part of permission request.

Add followin dependencies in build.gradle - androidx.activity, version 1.2.0+ and androidx.fragment, version 1.3.0+.

  • single permission, use RequestPermission.
  • multiple permissions, use RequestMultiplePermissions.

Follow these steps:

  • In your activity or fragment's initialization logic, pass in an implementation of ActivityResultCallback into a call to registerForActivityResult(). The ActivityResultCallback defines how your app handles the user's response to the permission request.
    Keep a reference to the return value of registerForActivityResult(), which is of type ActivityResultLauncher.

  • To display the system permissions dialog when necessary, call the launch() method on the instance of ActivityResultLauncher that you saved in the previous step.
    After launch() is called, the system permissions dialog appears. When the user makes a choice, the system asynchronously invokes your implementation of ActivityResultCallback, which you defined in the previous step.

Full example code

MainActivity

package ee.taltech.permissionsdemo

import android.content.Context
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import android.content.pm.PackageManager
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat

class MainActivity : AppCompatActivity() {
companion object {
private val TAG = this::class.java.declaringClass!!.simpleName
}

// Register the permissions callback, which handles the user's response to the
// system permissions dialog. Save the return value, an instance of ActivityResultLauncher.
private val requestPermissionsLauncher =
registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { isGrantedMap: Map<String, Boolean> ->
isGrantedMap.forEach { (permission: String, isGranted: Boolean) ->
if (isGranted) {
// Permission was granted. Continue the action or workflow in your
// app.
Log.d(TAG, "Permission '$permission' was granted by the user")
} else {
// Explain to the user that the feature is unavailable because the
// feature requires a permission that the user has denied. At the
// same time, respect the user's decision. Don't link to system
// settings in an effort to convince the user to change their
// decision.
Log.w(TAG, "Permission '$permission' was DENIED by the user")
}
}

}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val permissionsInManifest = retrieveManifestPermissions(applicationContext)
handleAppPermissions(permissionsInManifest)

Log.d(TAG, "onCreate DONE!")
}

private fun retrieveManifestPermissions(context: Context): Array<String> {
val pkgName = context.getPackageName()
try {
return context
.packageManager
.getPackageInfo(pkgName, PackageManager.GET_PERMISSIONS)
.requestedPermissions
} catch (e: PackageManager.NameNotFoundException) {
return emptyArray<String>()
// Better to throw a custom exception since this should never happen unless the API has changed somehow.
}
}

private fun handleAppPermissions(permissions: Array<String>) {
val missingPermissions = arrayListOf<String>()
permissions.forEach { permission ->
// https://kotlinlang.org/docs/coding-conventions.html#if-versus-when
when {
ContextCompat.checkSelfPermission(
applicationContext,
permission
) == PackageManager.PERMISSION_GRANTED -> {
// You can now use the API that requires the permission. Attach locationListener etc.
Log.d(TAG, "Permission '$permission' is already granted")
}

ActivityCompat.shouldShowRequestPermissionRationale(
this, permission
) -> {
Log.w(TAG, "Permission rationale is needed for: '$permission'")
showAndHandlePermissionRationale(permission)
}

else -> {
Log.d(TAG, "Permission '$permission' not yet granted")
missingPermissions.add(permission)
}
}
}

// request all the missing permissions
if (missingPermissions.isNotEmpty()) {
Log.d(TAG, "Requesting missing permissions")
requestPermissionsLauncher.launch(missingPermissions.toTypedArray())
}
}


private fun showAndHandlePermissionRationale(permission: String) {
// https://developer.android.com/develop/ui/views/components/dialogs
val builder: AlertDialog.Builder = AlertDialog.Builder(this)
builder
.setTitle("Permission required")
.setMessage("Permission '$permission' is required for app to work!")
.setPositiveButton("OK, Grant") { _, _ ->
requestPermissionsLauncher.launch(arrayOf(permission))
}
.setNegativeButton("NO") { _, _ ->
Toast.makeText(
this,
"Cannot work without '$permission' permission!",
Toast.LENGTH_LONG
).show()
// user denied the premission
// show error and redirect to settings
}

val dialog: AlertDialog = builder.create()
dialog.show()
}

}

Inspect and handle permissions while debugging

Inspect

adb shell dumpsys package PACKAGE_NAME

Remove user denial

adb shell pm clear-permission-flags PACKAGE_NAME PERMISSION_NAME user-set user-fixed

Revoke run-time permission

adb shell pm revoke PACKAGE_NAME PERMISSION_NAME

Grant run-time permission

adb shell pm grant PACKAGE_NAME PERMISSION_NAME

Uninstall the app

adb shell pm uninstall PACKAGE_NAME
adb shell pm clear PACKAGE_NAME

On osx adb is located in ~/Library/Android/sdk/platform-tools/

Handling location permissions

Location has several possible permissions.

  • Foreground location
  • Background location

Foreground location - app requires location info while activity is visible OR you are running foreground service (requires persistent notification).

Background location - app constantly shares location with other users or uses the Geofencing API. The system considers your app to be using background location if it accesses the device's current location in any situation other than the ones described in the foreground location.

    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

Examples of background locations apps:

  • Family location sharing app, feature lets users continuously share location with family members.
  • IoT app, feature lets users configure their home devices such that they turn off when the user leaves their home and turn back on when the user returns home.