📱 Device Features · Lesson 35
Runtime Permissions
Normal vs dangerous permissions
| Type | Examples | What you do |
|---|---|---|
| Normal | INTERNET, VIBRATE, ACCESS_NETWORK_STATE | Declare in manifest — granted automatically |
| Dangerous (runtime) | CAMERA, location, RECORD_AUDIO, READ_CONTACTS, POST_NOTIFICATIONS (13+) | Declare and ask the user while the app runs |
| Not needed any more | Picking photos (Photo Picker), sharing (intents), dialing (ACTION_DIAL) | Use the system UI instead |
The permission flow
Step 1: Declare in the manifest
XML
<!-- AndroidManifest.xml --> <uses-permission android:name="android.permission.INTERNET" /> <!-- normal --> <uses-permission android:name="android.permission.CAMERA" /> <!-- dangerous --> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- dangerous, API 33+ --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Step 2: Check and request
Kotlin
class ScanActivity : AppCompatActivity() {
// 1. register BEFORE the activity is started (as a property)
private val cameraPermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
if (granted) openCamera()
else Snackbar.make(binding.root, "Camera permission is needed to scan", Snackbar.LENGTH_LONG).show()
}
private fun onScanClicked() {
when {
// 2. already granted?
ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) ==
PackageManager.PERMISSION_GRANTED -> openCamera()
// 3. user denied once → explain why first
shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> showRationale()
// 4. ask
else -> cameraPermission.launch(Manifest.permission.CAMERA)
}
}
}Step 3: Explain and handle denial
Kotlin
private fun showRationale() {
MaterialAlertDialogBuilder(this)
.setTitle("Allow camera?")
.setMessage("We use the camera only to scan your ID card. Photos stay on your phone.")
.setNegativeButton("Not now", null)
.setPositiveButton("Continue") { _, _ -> cameraPermission.launch(Manifest.permission.CAMERA) }
.show()
}
// Permanently denied → open the app's settings page
private fun openAppSettings() {
startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", packageName, null)))
}Several permissions at once
Kotlin
private val locationPermissions = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { result ->
val fine = result[Manifest.permission.ACCESS_FINE_LOCATION] == true
val coarse = result[Manifest.permission.ACCESS_COARSE_LOCATION] == true
when {
fine -> startPreciseLocation()
coarse -> startApproximateLocation()
else -> showNoLocationMessage()
}
}
locationPermissions.launch(arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
))Real example: notifications (Android 13+)
Kotlin – Student Diary
// Student Diary – MainActivity
private val notificationPermission =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
if (!granted) {
Snackbar.make(binding.root, R.string.notifications_off, Snackbar.LENGTH_LONG).show()
}
}
private fun askNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !NotificationHelper.canNotify(this)) {
notificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
// NotificationHelper
fun canNotify(context: Context): Boolean =
Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) ==
PackageManager.PERMISSION_GRANTEDCommon mistakes
- Asking for every permission at app start → users deny; ask when the feature is used.
- Calling
registerForActivityResultinside a click → crash; register as a property. - Forgetting the manifest entry → the dialog never shows and the result is always false.
- Requesting POST_NOTIFICATIONS on Android 12 or lower → not needed; check the SDK version.
Summary
- Normal permissions: manifest only. Dangerous: manifest + runtime request.
checkSelfPermission→ rationale →RequestPermissionlauncher → callback.- Prefer permission-free system UIs (Photo Picker, intents) when possible.
✅ Practice
Add a "Scan QR" button that asks for camera permission with a rationale dialog, and shows an "Open Settings" Snackbar action when the permission is permanently denied.
Frequently Asked Questions
Do I need storage permission to pick a photo?
No. The Android Photo Picker (PickVisualMedia) gives your app access to the chosen image without any permission.
What happens after the user denies twice?
On Android 11+ the system stops showing the dialog ("don't ask again"). The request returns false immediately; guide the user to the app's Settings page instead.
Is INTERNET a runtime permission?
No. INTERNET is a normal permission: declaring it in the manifest is enough.
