1 package host.exp.exponent.notifications 2 3 import android.app.Notification 4 import android.app.PendingIntent 5 import android.content.Context 6 import android.os.Build 7 import android.os.Looper 8 import androidx.core.app.NotificationCompat 9 import androidx.core.app.RemoteInput 10 import com.raizlabs.android.dbflow.sql.language.SQLite 11 import com.raizlabs.android.dbflow.sql.language.Select 12 import host.exp.exponent.kernel.KernelConstants 13 import java.util.* 14 15 object NotificationActionCenter { 16 const val KEY_TEXT_REPLY = "notification_remote_input" 17 18 @Synchronized putCategorynull19 @JvmStatic fun putCategory(categoryId: String?, actions: List<MutableMap<String?, Any?>>) { 20 throwExceptionIfOnMainThread() 21 22 for (i in actions.indices) { 23 val action = actions[i].apply { 24 this["categoryId"] = categoryId 25 } 26 ActionObject(action, i).save() 27 } 28 } 29 30 @Synchronized removeCategorynull31 @JvmStatic fun removeCategory(categoryId: String?) { 32 val actions = SQLite.select().from(ActionObject::class.java) 33 .where(ActionObject_Table.categoryId.eq(categoryId)) 34 .queryList() 35 36 for (actionObject in actions) { 37 actionObject.delete() 38 } 39 } 40 41 @Synchronized setCategorynull42 fun setCategory( 43 categoryId: String, 44 builder: NotificationCompat.Builder, 45 context: Context, 46 intentProvider: IntentProvider 47 ) { 48 throwExceptionIfOnMainThread() 49 50 // Expo Go has a permanent notification, so we have to set max priority in order to show up buttons 51 builder.priority = Notification.PRIORITY_MAX 52 53 val actions = Select().from(ActionObject::class.java) 54 .where(ActionObject_Table.categoryId.eq(categoryId)) 55 .orderBy(ActionObject_Table.position, true) 56 .queryList() 57 58 for (actionObject in actions) { 59 addAction(builder, actionObject, intentProvider, context) 60 } 61 } 62 addActionnull63 private fun addAction( 64 builder: NotificationCompat.Builder, 65 actionObject: ActionObject, 66 intentProvider: IntentProvider, 67 context: Context 68 ) { 69 val intent = intentProvider.provide().apply { 70 putExtra(KernelConstants.NOTIFICATION_ACTION_TYPE_KEY, actionObject.actionId) 71 } 72 // We're defaulting to the behaviour prior API 31 (mutable) even though Android recommends immutability 73 val mutableFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0 74 val pendingIntent = PendingIntent.getActivity( 75 context, 76 UUID.randomUUID().hashCode(), 77 intent, 78 PendingIntent.FLAG_UPDATE_CURRENT or mutableFlag 79 ) 80 val actionBuilder = NotificationCompat.Action.Builder( 81 0, 82 actionObject.buttonTitle, 83 pendingIntent 84 ) 85 86 if (actionObject.isShouldShowTextInput) { 87 actionBuilder.addRemoteInput( 88 RemoteInput.Builder(KEY_TEXT_REPLY) 89 .setLabel(actionObject.placeholder) 90 .build() 91 ) 92 } 93 94 builder.addAction(actionBuilder.build()) 95 } 96 throwExceptionIfOnMainThreadnull97 private fun throwExceptionIfOnMainThread() { 98 if (Looper.myLooper() == Looper.getMainLooper()) { 99 throw RuntimeException("Do not use NotificationActionCenter class on the main thread!") 100 } 101 } 102 } 103