Today, We will explore the New notification 🔔 runtime permission that was added in Android Tiramisu(13).
- This permission is required for non-exempt notifications
- Its protection level is dangerous ⛔️
- Constant value:
public static final String POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS"; |
App behavior based on Action
- Allow:
when the user taps on allow app can do the following:
– Send notifications 🔔
– All notification channels of an app are allowed.
– The app can post notifications related to foreground services. - Don’t allow
when the user taps on don’t allow:
– App can’t send the notification 🔕
– All notification channels are blocked - Dismissed the dialog without any action
– If the app is eligible for a temporary notification permission grant, the system preserves the temporary grant otherwise, the app can’t send notifications.
Behavior on newly installed apps
If the device is running Android13 then the app’s notifications are off bydefault
When permission dialog shows up it depends on the targetSdk
of the app
- Android13: We have full control when we want to ask the user for permission
- Android 12L or lower: The system will show the permission dialog when the app creates its first notification channel
Effects on updates to existing apps
The system grants temporary permission
to the app if it falls under the category of eligible apps.
The length of a temporary grant depends on the targetSDK
- Android13: valid until the first-time app launches the activity
- Android 12L or lower: valid until the user selects an option in the permission dialog
If the user dismiss the permission dialog without selecting an option, the system will persist the temporary grant
Let’s see it in action
Notification Permission Dialog
- Declare the
permission
in the app’smanifest
file
<?xml version="1.0" encoding="utf-8"?> | |
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapplication"> | |
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> | |
<application> | |
</application> | |
</manifest> |
2. Ask for permission from the user
package com.example.myapplication | |
import android.Manifest | |
import android.content.Context | |
import android.content.Intent | |
import android.content.pm.PackageManager | |
import android.net.Uri | |
import android.os.Build | |
import android.os.Bundle | |
import android.provider.Settings | |
import android.util.Log | |
import android.widget.Button | |
import android.widget.TextView | |
import androidx.activity.result.contract.ActivityResultContracts | |
import androidx.appcompat.app.AppCompatActivity | |
import androidx.core.content.ContextCompat | |
import com.google.android.material.snackbar.Snackbar | |
/** | |
* @author Nav Singh | |
*/ | |
class MainActivity : AppCompatActivity() { | |
private val requestPermissionLauncher = registerForActivityResult( | |
ActivityResultContracts.RequestPermission() | |
) { isGranted: Boolean -> | |
if (isGranted) { | |
// Permission is granted. Continue the action or workflow in your | |
// app. | |
sendNotification(this) | |
} else { | |
// Explain to the user that the feature is unavailable because the | |
// features 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. | |
} | |
} | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContentView(R.layout.activity_main) | |
val message = intent?.getStringExtra(NOTIFICATION_MESSAGE_TAG) | |
findViewById<TextView>(R.id.tv_message).text = message | |
findViewById<Button>(R.id.click).setOnClickListener { | |
when { | |
ContextCompat.checkSelfPermission( | |
this, Manifest.permission.POST_NOTIFICATIONS | |
) == PackageManager.PERMISSION_GRANTED -> { | |
// You can use the API that requires the permission. | |
Log.e(TAG, "onCreate: PERMISSION GRANTED") | |
sendNotification(this) | |
} | |
shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) -> { | |
Snackbar.make( | |
findViewById(R.id.parent_layout), | |
"Notification blocked", | |
Snackbar.LENGTH_LONG | |
).setAction("Settings") { | |
// Responds to click on the action | |
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) | |
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) | |
val uri: Uri = Uri.fromParts("package", packageName, null) | |
intent.data = uri | |
startActivity(intent) | |
}.show() | |
} | |
else -> { | |
// The registered ActivityResultCallback gets the result of this request | |
requestPermissionLauncher.launch( | |
Manifest.permission.POST_NOTIFICATIONS | |
) | |
} | |
} | |
} | |
} | |
companion object { | |
const val TAG = "MainActivity" | |
const val NOTIFICATION_MESSAGE_TAG = "message from notification" | |
fun newIntent(context: Context) = Intent(context, MainActivity::class.java).apply { | |
putExtra( | |
NOTIFICATION_MESSAGE_TAG, "Hi ☕\uD83C\uDF77\uD83C\uDF70" | |
) | |
} | |
} | |
} |
3. Create and send Notification
package com.example.myapplication | |
import android.app.NotificationChannel | |
import android.app.NotificationManager | |
import android.app.PendingIntent.FLAG_IMMUTABLE | |
import android.content.Context | |
import android.os.Build | |
import androidx.core.app.NotificationCompat | |
import androidx.core.app.TaskStackBuilder | |
const val NOTIFICATION_CHANNEL_ID = BuildConfig.APPLICATION_ID + ".channel" | |
/** | |
* @author Nav Singh | |
*/ | |
fun sendNotification(context: Context) { | |
val notificationManager = context | |
.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager | |
// We need to create a NotificationChannel associated with our CHANNEL_ID before sending a notification. | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && | |
notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID) == null | |
) { | |
val name = context.getString(R.string.app_name) | |
val channel = NotificationChannel( | |
NOTIFICATION_CHANNEL_ID, | |
name, | |
NotificationManager.IMPORTANCE_DEFAULT | |
) | |
notificationManager.createNotificationChannel(channel) | |
} | |
val intent = MainActivity.newIntent(context.applicationContext) | |
// create a pending intent that opens MainActivity when the user clicks on the notification | |
val stackBuilder = TaskStackBuilder.create(context) | |
.addParentStack(MainActivity::class.java) | |
.addNextIntent(intent) | |
val notificationPendingIntent = stackBuilder | |
.getPendingIntent(getUniqueId(), FLAG_IMMUTABLE) | |
// build the notification object with the data to be shown | |
val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) | |
.setSmallIcon(R.mipmap.ic_launcher) | |
.setContentTitle("Title") | |
.setContentText("Content goes here") | |
.setContentIntent(notificationPendingIntent) | |
.setAutoCancel(true) | |
.build() | |
notificationManager.notify(getUniqueId(), notification) | |
} | |
private fun getUniqueId() = ((System.currentTimeMillis() % 10000).toInt()) |
Demo
Allow notification permission
Job Offers
Deny notification permission
Check full code here
References
Android Developers Blog: Android 13 Developer Preview 2 (googleblog.com)
Notification runtime permission | Android 13 Developer Preview | Android Developers
Set up the Android 13 SDK | Android 13 Developer Preview | Android Developers
Manifest.permission | Android Developers
Notification runtime permission | Android 13 Developer Preview | Android Developers
😊😊 👏👏👏👏 HAPPY CODING 👏👏👏👏 😊😊
Stay in touch
This article was originally published on proandroiddev.com on March 20, 2022