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