-
Notifications
You must be signed in to change notification settings - Fork 367
Additional notification code snippets migration #929
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -17,24 +17,33 @@ | |||||
| package com.example.compose.snippets.notifications | ||||||
|
|
||||||
| import android.Manifest | ||||||
| import android.app.NotificationChannel | ||||||
| import android.app.NotificationChannelGroup | ||||||
| import android.app.NotificationManager | ||||||
| import android.app.PendingIntent | ||||||
| import android.content.BroadcastReceiver | ||||||
| import android.content.Context | ||||||
| import android.content.Intent | ||||||
| import android.content.pm.PackageManager | ||||||
| import android.graphics.Bitmap | ||||||
| import android.graphics.BitmapFactory | ||||||
| import android.graphics.drawable.Icon | ||||||
| import android.os.Build | ||||||
| import androidx.activity.ComponentActivity | ||||||
| import androidx.activity.compose.rememberLauncherForActivityResult | ||||||
| import androidx.activity.result.contract.ActivityResultContracts | ||||||
| import androidx.annotation.OptIn | ||||||
| import androidx.annotation.RequiresPermission | ||||||
| import androidx.compose.material3.Text | ||||||
| import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.ui.platform.LocalContext | ||||||
| import androidx.core.app.ActivityCompat | ||||||
| import androidx.core.app.NotificationCompat | ||||||
| import androidx.core.app.NotificationCompat.EXTRA_NOTIFICATION_ID | ||||||
| import androidx.core.app.NotificationManagerCompat | ||||||
| import androidx.core.app.PendingIntentCompat | ||||||
| import androidx.core.app.Person | ||||||
| import androidx.core.app.RemoteInput | ||||||
| import androidx.core.content.ContextCompat | ||||||
| import androidx.media3.common.util.UnstableApi | ||||||
| import androidx.media3.exoplayer.ExoPlayer | ||||||
|
|
@@ -43,6 +52,8 @@ import androidx.media3.session.MediaStyleNotificationHelper | |||||
| import com.example.compose.snippets.R | ||||||
| import com.example.compose.snippets.touchinput.Button | ||||||
|
|
||||||
| val CHANNEL_ID = "channelId" | ||||||
|
|
||||||
| @Composable | ||||||
| fun NotificationSnippets(context: Context) { | ||||||
| // [START android_notification_authenticated_action] | ||||||
|
|
@@ -61,6 +72,200 @@ fun NotificationSnippets(context: Context) { | |||||
| // [END android_notification_authenticated_action] | ||||||
| } | ||||||
|
|
||||||
| fun createNotification(context: Context) { | ||||||
| // [START android_notification_create] | ||||||
| val textTitle = "Title" | ||||||
| val textContent = "Content" | ||||||
| val builder = NotificationCompat.Builder(context, CHANNEL_ID) | ||||||
| .setSmallIcon(R.drawable.ic_logo) | ||||||
| .setContentTitle(textTitle) | ||||||
| .setContentText(textContent) | ||||||
| .setPriority(NotificationCompat.PRIORITY_DEFAULT) | ||||||
| // [END android_notification_create] | ||||||
| } | ||||||
|
|
||||||
| fun createNotificationWithStyle(context: Context) { | ||||||
| // [START android_notification_set_style] | ||||||
| val builder = NotificationCompat.Builder(context, CHANNEL_ID) | ||||||
| .setSmallIcon(R.drawable.ic_logo) | ||||||
| .setContentTitle("My notification") | ||||||
| .setContentText("Much longer text that cannot fit one line...") | ||||||
| .setStyle(NotificationCompat.BigTextStyle() | ||||||
| .bigText("Much longer text that cannot fit one line...")) | ||||||
| .setPriority(NotificationCompat.PRIORITY_DEFAULT) | ||||||
| // [END android_notification_set_style] | ||||||
| } | ||||||
|
|
||||||
| // [START android_notification_create_channel] | ||||||
| fun createNotificationChannel(context: Context) { | ||||||
| // Create the NotificationChannel, but only on API 26+ because | ||||||
| // the NotificationChannel class is not in the Support Library. | ||||||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||||||
| val name = context.getString(R.string.channel_name) | ||||||
| val descriptionText = context.getString(R.string.channel_description) | ||||||
| val importance = NotificationManager.IMPORTANCE_DEFAULT | ||||||
| val channel = NotificationChannel(CHANNEL_ID, name, importance).apply { | ||||||
| description = descriptionText | ||||||
| } | ||||||
| // Register the channel with the system. | ||||||
| val notificationManager: NotificationManager = | ||||||
| context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager | ||||||
| notificationManager.createNotificationChannel(channel) | ||||||
| } | ||||||
| } | ||||||
| // [END android_notification_create_channel] | ||||||
|
|
||||||
| fun createNotificationTapAction(context: Context) { | ||||||
| // [START android_notification_tap_action] | ||||||
| // Create an explicit intent for an Activity in your app. | ||||||
| val intent = Intent(context, AlertDetails::class.java).apply { | ||||||
| flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK | ||||||
| } | ||||||
| val pendingIntent: PendingIntent = | ||||||
| PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) | ||||||
|
|
||||||
| val builder = NotificationCompat.Builder(context, CHANNEL_ID) | ||||||
| .setSmallIcon(R.drawable.ic_logo) | ||||||
| .setContentTitle("My notification") | ||||||
| .setContentText("Hello World!") | ||||||
| .setPriority(NotificationCompat.PRIORITY_DEFAULT) | ||||||
| // Set the intent that fires when the user taps the notification. | ||||||
| .setContentIntent(pendingIntent) | ||||||
| .setAutoCancel(true) | ||||||
| // [END android_notification_tap_action] | ||||||
| } | ||||||
|
|
||||||
| fun showNotification(context: Context) { | ||||||
| val builder = NotificationCompat.Builder(context, CHANNEL_ID) | ||||||
| val notificationId = 1 // This is demonstrative and should be unique | ||||||
| // [START android_notification_show_notification] | ||||||
| with(NotificationManagerCompat.from(context)) { | ||||||
| if (ActivityCompat.checkSelfPermission( | ||||||
| context, | ||||||
| Manifest.permission.POST_NOTIFICATIONS | ||||||
| ) != PackageManager.PERMISSION_GRANTED | ||||||
| ) { | ||||||
| // TODO: Consider calling ActivityCompat#requestPermissions here | ||||||
| // to request the missing permissions, and then overriding | ||||||
| // public fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, | ||||||
| // grantResults: IntArray) | ||||||
| // to handle the case where the user grants the permission. See the documentation | ||||||
| // for ActivityCompat#requestPermissions for more details. | ||||||
|
|
||||||
| return@with | ||||||
| } | ||||||
| // notificationId is a unique int for each notification that you must define. | ||||||
| notify(notificationId, builder.build()) | ||||||
| // [END android_notification_show_notification] | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| fun addActionButton(context: Context) { | ||||||
| val pendingIntent = PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE) | ||||||
| // [START android_notification_add_action] | ||||||
| val ACTION_SNOOZE = "snooze" | ||||||
| val snoozeIntent = Intent(context, MyBroadcastReceiver::class.java).apply { | ||||||
| action = ACTION_SNOOZE | ||||||
| putExtra(EXTRA_NOTIFICATION_ID, 0) | ||||||
| } | ||||||
| val snoozePendingIntent: PendingIntent = | ||||||
| PendingIntent.getBroadcast(context, 0, snoozeIntent, PendingIntent.FLAG_IMMUTABLE) | ||||||
| val builder = NotificationCompat.Builder(context, CHANNEL_ID) | ||||||
| .setSmallIcon(R.drawable.ic_logo) | ||||||
| .setContentTitle("My notification") | ||||||
| .setContentText("Hello World!") | ||||||
| .setPriority(NotificationCompat.PRIORITY_DEFAULT) | ||||||
| .setContentIntent(pendingIntent) | ||||||
| .addAction(R.drawable.snooze, context.getString(R.string.snooze), | ||||||
| snoozePendingIntent) | ||||||
| // [END android_notification_add_action] | ||||||
|
|
||||||
| // [START android_notification_add_reply] | ||||||
| // Key for the string that's delivered in the action's intent. | ||||||
| val KEY_TEXT_REPLY = "key_text_reply" | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is recommended to use the constant
Suggested change
|
||||||
| val replyLabel: String = context.resources.getString(R.string.reply_label) | ||||||
| val remoteInput: RemoteInput = RemoteInput.Builder(KEY_TEXT_REPLY).run { | ||||||
| setLabel(replyLabel) | ||||||
| build() | ||||||
| } | ||||||
| // [END android_notification_add_reply] | ||||||
|
|
||||||
| val conversationId = 1 // This is demonstrative and should be unique. | ||||||
| // [START android_notification_add_reply_pending_intent] | ||||||
| // Build a PendingIntent for the reply action to trigger. | ||||||
| val replyPendingIntent: PendingIntent = | ||||||
| PendingIntent.getBroadcast(context, | ||||||
| conversationId, | ||||||
| getMessageReplyIntent(conversationId), | ||||||
| PendingIntent.FLAG_MUTABLE) | ||||||
| // [END android_notification_add_reply_pending_intent] | ||||||
|
|
||||||
| // [START android_notification_add_reply_action] | ||||||
| // Create the reply action and add the remote input. | ||||||
| val action: NotificationCompat.Action = | ||||||
| NotificationCompat.Action.Builder(R.drawable.reply, | ||||||
| context.getString(R.string.reply_label), replyPendingIntent) | ||||||
| .addRemoteInput(remoteInput) | ||||||
| .build() | ||||||
| // [END android_notification_add_reply_action] | ||||||
| } | ||||||
|
|
||||||
| // [START android_notification_retrieve_user_input] | ||||||
| private fun getMessageText(intent: Intent): CharSequence? { | ||||||
| return RemoteInput.getResultsFromIntent(intent)?.getCharSequence(ReplyReceiver.KEY_TEXT_REPLY) | ||||||
| } | ||||||
| // [START android_notification_retrieve_user_input] | ||||||
|
|
||||||
| fun getMessageReplyIntent(conversationId: Any): Intent { | ||||||
| // This is for demonstrative purposes. | ||||||
| TODO("Not yet implemented") | ||||||
| } | ||||||
|
|
||||||
| @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS) | ||||||
| fun handledReply(context: Context) { | ||||||
| val notificationId = 1 // This is demonstrative and should be unique. | ||||||
| // [START android_notification_handled_reply] | ||||||
| // Build a new notification, which informs the user that the system | ||||||
| // handled their interaction with the previous notification. | ||||||
| val repliedNotification = NotificationCompat.Builder(context, CHANNEL_ID) | ||||||
| .setSmallIcon(R.drawable.message) | ||||||
| .setContentText(context.getString(R.string.replied)) | ||||||
| .build() | ||||||
|
|
||||||
| // Issue the new notification. | ||||||
| NotificationManagerCompat.from(context).notify(notificationId, repliedNotification) | ||||||
| // [END android_notification_handled_reply] | ||||||
| } | ||||||
|
|
||||||
| fun retrieveOtherData(context: Context) { | ||||||
| // [START android_notification_remote_input_retrieve_data] | ||||||
| val replyLabel: String = context.resources.getString(R.string.reply_label) | ||||||
| val remoteInput: RemoteInput = RemoteInput.Builder(ReplyReceiver.KEY_REPLY).run { | ||||||
| setLabel(replyLabel) | ||||||
| // Allow for image data types in the input. | ||||||
| // This method can be used again to allow for other data types. | ||||||
| setAllowDataType("image/*", true) | ||||||
| build() | ||||||
| } | ||||||
| // [END android_notification_remote_input_retrieve_data] | ||||||
| } | ||||||
|
|
||||||
| fun fullScreenIntent(context: Context) { | ||||||
| // [START android_notification_full_screen_intent] | ||||||
| val fullScreenIntent = Intent(context, ImportantActivity::class.java) | ||||||
| val fullScreenPendingIntent = PendingIntent.getActivity(context, 0, | ||||||
| fullScreenIntent, PendingIntent.FLAG_IMMUTABLE) | ||||||
|
|
||||||
| val builder = NotificationCompat.Builder(context, CHANNEL_ID) | ||||||
| .setSmallIcon(R.drawable.ic_logo) | ||||||
| .setContentTitle("My notification") | ||||||
| .setContentText("Hello World!") | ||||||
| .setPriority(NotificationCompat.PRIORITY_DEFAULT) | ||||||
| .setFullScreenIntent(fullScreenPendingIntent, true) | ||||||
| // [END android_notification_full_screen_intent] | ||||||
| } | ||||||
|
|
||||||
|
|
||||||
| @Composable | ||||||
| fun NotificationSnippetRequestPostPermission() { | ||||||
| // [START android_notification_request_post_permission] | ||||||
|
|
@@ -228,3 +433,18 @@ val messages = listOf( | |||||
| Person.Builder().setName("Frank").build() | ||||||
| ) | ||||||
| ) | ||||||
|
|
||||||
| // For demonstrative purposes only when used for a full screen intent. | ||||||
| class ImportantActivity : ComponentActivity() { | ||||||
| } | ||||||
|
|
||||||
| // For demonstrative purposes only for launching from a tap action. | ||||||
| class AlertDetails : ComponentActivity() { | ||||||
| } | ||||||
|
|
||||||
| // For demonstrative purposes only for handling a snooze action. | ||||||
| class MyBroadcastReceiver : BroadcastReceiver() { | ||||||
| override fun onReceive(context: Context?, intent: Intent?) { | ||||||
| TODO("Not yet implemented") | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| /* | ||
| * Copyright 2026 The Android Open Source Project | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package com.example.compose.snippets.notifications | ||
|
|
||
| import android.content.BroadcastReceiver | ||
| import android.content.Context | ||
| import android.content.Intent | ||
| import android.graphics.BitmapFactory | ||
| import android.net.Uri | ||
| import android.util.Log | ||
| import androidx.core.app.RemoteInput | ||
|
|
||
| // [START android_notification_reply_receiver] | ||
| class ReplyReceiver : BroadcastReceiver() { | ||
| override fun onReceive(context: Context, intent: Intent) { | ||
| val dataResults = RemoteInput.getDataResultsFromIntent(intent, KEY_REPLY) | ||
| val imageUri: Uri? = dataResults?.get("image/*") as? Uri | ||
|
|
||
| if (imageUri != null) { | ||
| // Extract the image | ||
| try { | ||
| val inputStream = context.contentResolver.openInputStream(imageUri) | ||
| val bitmap = BitmapFactory.decodeStream(inputStream) | ||
| // Display the image | ||
| // ... | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The context.contentResolver.openInputStream(imageUri)?.use { inputStream ->
val bitmap = BitmapFactory.decodeStream(inputStream)
// Display the image
// ...
} |
||
| } catch (e: Exception) { | ||
| Log.e("ReplyReceiver", "Failed to process image URI", e) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| companion object { | ||
| const val KEY_REPLY = "key_reply" | ||
| const val KEY_TEXT_REPLY = "key_text_reply" | ||
| } | ||
| } | ||
| // [END android_notification_reply_receiver] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <!-- | ||
| Copyright 2026 The Android Open Source Project | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| https://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| --> | ||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| android:width="20dp" | ||
| android:height="20dp" | ||
| android:viewportWidth="960" | ||
| android:viewportHeight="960" | ||
| android:tint="?attr/colorControlNormal"> | ||
| <path | ||
| android:fillColor="@android:color/white" | ||
| android:pathData="M184,415.93Q171,402.86 171,383.93Q171,365 184.07,352Q197.14,339 216.07,339Q235,339 248,352.07Q261,365.14 261,384.07Q261,403 247.93,416Q234.86,429 215.93,429Q197,429 184,415.93ZM712,415.93Q699,402.86 699,383.93Q699,365 712.07,352Q725.14,339 744.07,339Q763,339 776,352.07Q789,365.14 789,384.07Q789,403 775.93,416Q762.86,429 743.93,429Q725,429 712,415.93ZM78,516L78,516L78,516L78,516L78,516L78,516L78,516Q78,516 78,516Q78,516 78,516ZM912,516L912,516L912,516L912,516Q912,516 912,516Q912,516 912,516L912,516L912,516L912,516L912,516ZM168,864L96,864L96,696L264,696Q204,696 162.5,654Q121,612 121,552L193,552Q193,581.7 213.5,602.85Q234,624 263.96,624L263.96,504L362,504L330,369Q309,281 239,224.5Q169,168 78,168L48,168L48,96L78,96Q192,96 281.5,165Q371,234 398,345L444,530Q449,547.48 438.22,561.74Q427.44,576 410,576L336,576L336,696Q336,725.7 314.85,746.85Q293.7,768 264,768L168,768L168,864ZM864,864L792,864L792,768L696,768Q666.3,768 645.15,746.85Q624,725.7 624,696L624,576L552,576Q534,576 523,561Q512,546 517,529L564,335Q592,232 678,164Q764,96 876,96L912,96L912,168L876,168Q791,168 722,221Q653,274 630,369L598,504L696.04,504L696.04,624Q726,624 747.09,602.85Q768.19,581.7 768.19,552L840,552Q840,612 798,654Q756,696 696,696L864,696L864,864ZM336,696L336,696L336,624L336,624L336,696ZM624,696L624,624L624,624L624,696Z"/> | ||
| </vector> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <!-- | ||
| Copyright 2026 The Android Open Source Project | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| https://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| --> | ||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| android:width="20dp" | ||
| android:height="20dp" | ||
| android:viewportWidth="960" | ||
| android:viewportHeight="960" | ||
| android:tint="?attr/colorControlNormal" | ||
| android:autoMirrored="true"> | ||
| <path | ||
| android:fillColor="@android:color/white" | ||
| android:pathData="M744,750L744,606Q744,556 709,521Q674,486 624,486L282,486L405,609L354,660L144,450L354,240L405,291L282,414L624,414Q704,414 760,470Q816,526 816,606L816,750L744,750Z"/> | ||
| </vector> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For better type safety and consistency with modern Android practices, use
context.getSystemService(NotificationManager::class.java). This avoids manual casting and is safer when the SDK version is 23 or higher.