1 /* <lambda>null2 * Copyright (C) 2019 The Android Open Source Project 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package com.example.android.bubbles.data 17 18 import android.app.Notification 19 import android.app.NotificationChannel 20 import android.app.NotificationManager 21 import android.app.PendingIntent 22 import android.app.Person 23 import android.content.Context 24 import android.content.Intent 25 import android.graphics.BitmapFactory 26 import android.graphics.drawable.Icon 27 import android.net.Uri 28 import androidx.annotation.WorkerThread 29 import com.example.android.bubbles.BubbleActivity 30 import com.example.android.bubbles.MainActivity 31 import com.example.android.bubbles.R 32 33 /** 34 * Handles all operations related to [Notification]. 35 */ 36 class NotificationHelper(private val context: Context) { 37 38 companion object { 39 /** 40 * The notification channel for messages. This is used for showing Bubbles. 41 */ 42 private const val CHANNEL_NEW_MESSAGES = "new_messages" 43 44 private const val REQUEST_CONTENT = 1 45 private const val REQUEST_BUBBLE = 2 46 } 47 48 private val notificationManager = context.getSystemService(NotificationManager::class.java) 49 50 fun setUpNotificationChannels() { 51 if (notificationManager.getNotificationChannel(CHANNEL_NEW_MESSAGES) == null) { 52 notificationManager.createNotificationChannel( 53 NotificationChannel( 54 CHANNEL_NEW_MESSAGES, 55 context.getString(R.string.channel_new_messages), 56 // The importance must be IMPORTANCE_HIGH to show Bubbles. 57 NotificationManager.IMPORTANCE_HIGH 58 ).apply { 59 description = context.getString(R.string.channel_new_messages_description) 60 } 61 ) 62 } 63 } 64 65 @WorkerThread 66 fun showNotification(chat: Chat, fromUser: Boolean) { 67 val icon = Icon.createWithAdaptiveBitmap( 68 BitmapFactory.decodeResource( 69 context.resources, 70 chat.contact.icon 71 ) 72 ) 73 val person = Person.Builder() 74 .setName(chat.contact.name) 75 .setIcon(icon) 76 .build() 77 val contentUri = Uri.parse("https://android.example.com/chat/${chat.contact.id}") 78 val builder = Notification.Builder(context, CHANNEL_NEW_MESSAGES) 79 // A notification can be shown as a bubble by calling setBubbleMetadata() 80 .setBubbleMetadata( 81 Notification.BubbleMetadata.Builder() 82 // The height of the expanded bubble. 83 .setDesiredHeight(context.resources.getDimensionPixelSize(R.dimen.bubble_height)) 84 // The icon of the bubble. 85 // TODO: The icon is not displayed in Android Q Beta 2. 86 .setIcon(icon) 87 .apply { 88 // When the bubble is explicitly opened by the user, we can show the bubble automatically 89 // in the expanded state. This works only when the app is in the foreground. 90 // TODO: This does not yet work in Android Q Beta 2. 91 if (fromUser) { 92 setAutoExpandBubble(true) 93 setSuppressInitialNotification(true) 94 } 95 } 96 // The Intent to be used for the expanded bubble. 97 .setIntent( 98 PendingIntent.getActivity( 99 context, 100 REQUEST_BUBBLE, 101 // Launch BubbleActivity as the expanded bubble. 102 Intent(context, BubbleActivity::class.java) 103 .setAction(Intent.ACTION_VIEW) 104 .setData(Uri.parse("https://android.example.com/chat/${chat.contact.id}")), 105 PendingIntent.FLAG_UPDATE_CURRENT 106 ) 107 ) 108 .build() 109 ) 110 // The user can turn off the bubble in system settings. In that case, this notification is shown as a 111 // normal notification instead of a bubble. Make sure that this notification works as a normal notification 112 // as well. 113 .setContentTitle(chat.contact.name) 114 .setSmallIcon(R.drawable.ic_message) 115 .setCategory(Notification.CATEGORY_MESSAGE) 116 .addPerson(person) 117 .setShowWhen(true) 118 // The content Intent is used when the user clicks on the "Open Content" icon button on the expanded bubble, 119 // as well as when the fall-back notification is clicked. 120 .setContentIntent( 121 PendingIntent.getActivity( 122 context, 123 REQUEST_CONTENT, 124 Intent(context, MainActivity::class.java) 125 .setAction(Intent.ACTION_VIEW) 126 .setData(contentUri), 127 PendingIntent.FLAG_UPDATE_CURRENT 128 ) 129 ) 130 131 if (fromUser) { 132 // This is a Bubble explicitly opened by the user. 133 builder.setContentText(context.getString(R.string.chat_with_contact, chat.contact.name)) 134 } else { 135 // Let's add some more content to the notification in case it falls back to a normal notification. 136 val lastOutgoingId = chat.messages.last { !it.isIncoming }.id 137 val newMessages = chat.messages.filter { message -> 138 message.id > lastOutgoingId 139 } 140 val lastMessage = newMessages.last() 141 builder 142 .setStyle( 143 if (lastMessage.photo != null) { 144 Notification.BigPictureStyle() 145 .bigPicture(BitmapFactory.decodeResource(context.resources, lastMessage.photo)) 146 .bigLargeIcon(icon) 147 .setSummaryText(lastMessage.text) 148 } else { 149 Notification.MessagingStyle(person) 150 .apply { 151 for (message in newMessages) { 152 addMessage(message.text, message.timestamp, person) 153 } 154 } 155 .setGroupConversation(false) 156 } 157 ) 158 .setContentText(newMessages.joinToString("\n") { it.text }) 159 .setWhen(newMessages.last().timestamp) 160 } 161 162 notificationManager.notify(chat.contact.id.toInt(), builder.build()) 163 } 164 165 fun dismissNotification(id: Long) { 166 notificationManager.cancel(id.toInt()) 167 } 168 169 fun canBubble(): Boolean { 170 val channel = notificationManager.getNotificationChannel(CHANNEL_NEW_MESSAGES) 171 return notificationManager.areBubblesAllowed() && channel.canBubble() 172 } 173 } 174