1
+ package com .hoxfon .react .RNTwilioVoice ;
2
+
3
+ import android .annotation .TargetApi ;
4
+ import android .app .Notification ;
5
+ import android .app .NotificationChannel ;
6
+ import android .app .NotificationManager ;
7
+ import android .app .PendingIntent ;
8
+ import android .app .Service ;
9
+ import android .content .Context ;
10
+ import android .content .Intent ;
11
+ import android .graphics .Color ;
12
+ import android .os .Build ;
13
+ import android .os .Bundle ;
14
+ import android .os .IBinder ;
15
+ import android .util .Log ;
16
+
17
+ import androidx .core .app .NotificationCompat ;
18
+ import androidx .lifecycle .Lifecycle ;
19
+ import androidx .lifecycle .ProcessLifecycleOwner ;
20
+ import androidx .localbroadcastmanager .content .LocalBroadcastManager ;
21
+
22
+ import com .twilio .voice .CallInvite ;
23
+
24
+ import static com .hoxfon .react .RNTwilioVoice .CallNotificationManager .getMainActivityClass ;
25
+ import static com .hoxfon .react .RNTwilioVoice .Constants .ACTION_ACCEPT ;
26
+ import static com .hoxfon .react .RNTwilioVoice .Constants .ACTION_CANCEL_CALL ;
27
+ import static com .hoxfon .react .RNTwilioVoice .Constants .ACTION_INCOMING_CALL ;
28
+ import static com .hoxfon .react .RNTwilioVoice .Constants .ACTION_INCOMING_CALL_NOTIFICATION ;
29
+ import static com .hoxfon .react .RNTwilioVoice .Constants .ACTION_REJECT ;
30
+ import static com .hoxfon .react .RNTwilioVoice .Constants .CALL_SID_KEY ;
31
+ import static com .hoxfon .react .RNTwilioVoice .Constants .INCOMING_CALL_INVITE ;
32
+ import static com .hoxfon .react .RNTwilioVoice .Constants .INCOMING_CALL_NOTIFICATION_ID ;
33
+ import static com .hoxfon .react .RNTwilioVoice .TwilioVoiceModule .TAG ;
34
+
35
+ public class IncomingCallNotificationService extends Service {
36
+
37
+ @ Override
38
+ public int onStartCommand (Intent intent , int flags , int startId ) {
39
+ String action = intent .getAction ();
40
+
41
+ CallInvite callInvite = intent .getParcelableExtra (INCOMING_CALL_INVITE );
42
+ int notificationId = intent .getIntExtra (INCOMING_CALL_NOTIFICATION_ID , 0 );
43
+
44
+ switch (action ) {
45
+ case ACTION_INCOMING_CALL :
46
+ handleIncomingCall (callInvite , notificationId );
47
+ break ;
48
+ case ACTION_ACCEPT :
49
+ accept (callInvite , notificationId );
50
+ break ;
51
+ case ACTION_REJECT :
52
+ reject (callInvite );
53
+ break ;
54
+ case ACTION_CANCEL_CALL :
55
+ handleCancelledCall (intent );
56
+ break ;
57
+ default :
58
+ break ;
59
+ }
60
+ return START_NOT_STICKY ;
61
+ }
62
+
63
+ @ Override
64
+ public IBinder onBind (Intent intent ) {
65
+ return null ;
66
+ }
67
+
68
+ private Notification createNotification (CallInvite callInvite , int notificationId , int channelImportance ) {
69
+ Intent intent = new Intent (this , getMainActivityClass (this ));
70
+ intent .setAction (ACTION_INCOMING_CALL_NOTIFICATION );
71
+ intent .putExtra (INCOMING_CALL_NOTIFICATION_ID , notificationId );
72
+ intent .putExtra (INCOMING_CALL_INVITE , callInvite );
73
+ intent .addFlags (Intent .FLAG_ACTIVITY_CLEAR_TOP );
74
+ PendingIntent pendingIntent =
75
+ PendingIntent .getActivity (this , notificationId , intent , PendingIntent .FLAG_UPDATE_CURRENT );
76
+ /*
77
+ * Pass the notification id and call sid to use as an identifier to cancel the
78
+ * notification later
79
+ */
80
+ Bundle extras = new Bundle ();
81
+ extras .putString (CALL_SID_KEY , callInvite .getCallSid ());
82
+
83
+ String contextText = callInvite .getFrom () + " is calling." ;
84
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
85
+ // TODO make text configurable from app resources
86
+ return buildNotification (contextText ,
87
+ pendingIntent ,
88
+ extras ,
89
+ callInvite ,
90
+ notificationId ,
91
+ createChannel (channelImportance ));
92
+ } else {
93
+ return new NotificationCompat .Builder (this )
94
+ .setSmallIcon (R .drawable .ic_call_white_24dp )
95
+ .setContentTitle (getString (R .string .call_incoming ))
96
+ .setContentText (contextText )
97
+ .setAutoCancel (true )
98
+ .setExtras (extras )
99
+ .setContentIntent (pendingIntent )
100
+ .setGroup ("test_app_notification" )
101
+ .setColor (Color .rgb (214 , 10 , 37 ))
102
+ .build ();
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Build a notification.
108
+ *
109
+ * @param text the text of the notification
110
+ * @param pendingIntent the body, pending intent for the notification
111
+ * @param extras extras passed with the notification
112
+ * @return the builder
113
+ */
114
+ @ TargetApi (Build .VERSION_CODES .O )
115
+ private Notification buildNotification (String text ,
116
+ PendingIntent pendingIntent ,
117
+ Bundle extras ,
118
+ final CallInvite callInvite ,
119
+ int notificationId ,
120
+ String channelId ) {
121
+ Intent rejectIntent = new Intent (getApplicationContext (), IncomingCallNotificationService .class );
122
+ rejectIntent .setAction (ACTION_REJECT );
123
+ rejectIntent .putExtra (INCOMING_CALL_INVITE , callInvite );
124
+ rejectIntent .putExtra (INCOMING_CALL_NOTIFICATION_ID , notificationId );
125
+ PendingIntent piRejectIntent = PendingIntent .getService (getApplicationContext (), 0 , rejectIntent , PendingIntent .FLAG_UPDATE_CURRENT );
126
+
127
+ Intent acceptIntent = new Intent (getApplicationContext (), IncomingCallNotificationService .class );
128
+ acceptIntent .setAction (ACTION_ACCEPT );
129
+ acceptIntent .putExtra (INCOMING_CALL_INVITE , callInvite );
130
+ acceptIntent .putExtra (INCOMING_CALL_NOTIFICATION_ID , notificationId );
131
+ PendingIntent piAcceptIntent = PendingIntent .getService (getApplicationContext (), 0 , acceptIntent , PendingIntent .FLAG_UPDATE_CURRENT );
132
+
133
+ Notification .Builder builder =
134
+ new Notification .Builder (getApplicationContext (), channelId )
135
+ .setSmallIcon (R .drawable .ic_call_white_24dp )
136
+ .setContentTitle (getString (R .string .call_incoming ))
137
+ .setContentText (text )
138
+ .setCategory (Notification .CATEGORY_CALL )
139
+ .setExtras (extras )
140
+ .setAutoCancel (true )
141
+ .addAction (android .R .drawable .ic_menu_delete , getString (R .string .decline ), piRejectIntent )
142
+ .addAction (android .R .drawable .ic_menu_call , getString (R .string .answer ), piAcceptIntent )
143
+ .setFullScreenIntent (pendingIntent , true );
144
+
145
+ return builder .build ();
146
+ }
147
+
148
+ @ TargetApi (Build .VERSION_CODES .O )
149
+ private String createChannel (int channelImportance ) {
150
+ String channelId = Constants .VOICE_CHANNEL_HIGH_IMPORTANCE ;
151
+ if (channelImportance == NotificationManager .IMPORTANCE_LOW ) {
152
+ channelId = Constants .VOICE_CHANNEL_LOW_IMPORTANCE ;
153
+ }
154
+ NotificationChannel callInviteChannel = new NotificationChannel (channelId ,
155
+ "Incoming calls" , channelImportance );
156
+ callInviteChannel .setLightColor (Color .GREEN );
157
+ // TODO set sound for background incoming call
158
+ // callInviteChannel.setSound();
159
+ callInviteChannel .setLockscreenVisibility (Notification .VISIBILITY_PUBLIC );
160
+ NotificationManager notificationManager = (NotificationManager ) getSystemService (Context .NOTIFICATION_SERVICE );
161
+ notificationManager .createNotificationChannel (callInviteChannel );
162
+
163
+ return channelId ;
164
+ }
165
+
166
+ private void accept (CallInvite callInvite , int notificationId ) {
167
+ endForeground ();
168
+ Intent activeCallIntent = new Intent (this , getMainActivityClass (this ));
169
+ activeCallIntent .addFlags (Intent .FLAG_ACTIVITY_SINGLE_TOP );
170
+ activeCallIntent .addFlags (Intent .FLAG_ACTIVITY_NEW_TASK );
171
+ activeCallIntent .putExtra (INCOMING_CALL_INVITE , callInvite );
172
+ activeCallIntent .putExtra (INCOMING_CALL_NOTIFICATION_ID , notificationId );
173
+ activeCallIntent .setAction (ACTION_ACCEPT );
174
+ this .startActivity (activeCallIntent );
175
+ }
176
+
177
+ private void reject (CallInvite callInvite ) {
178
+ endForeground ();
179
+ callInvite .reject (getApplicationContext ());
180
+ }
181
+
182
+ private void handleCancelledCall (Intent intent ) {
183
+ endForeground ();
184
+ LocalBroadcastManager .getInstance (this ).sendBroadcast (intent );
185
+ }
186
+
187
+ private void handleIncomingCall (CallInvite callInvite , int notificationId ) {
188
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
189
+ setCallInProgressNotification (callInvite , notificationId );
190
+ }
191
+ sendCallInviteToActivity (callInvite , notificationId );
192
+ }
193
+
194
+ private void endForeground () {
195
+ stopForeground (true );
196
+ }
197
+
198
+ private void setCallInProgressNotification (CallInvite callInvite , int notificationId ) {
199
+ int importance = NotificationManager .IMPORTANCE_LOW ;
200
+ if (!isAppVisible ()) {
201
+ Log .i (TAG , "setCallInProgressNotification - app is NOT visible." );
202
+ importance = NotificationManager .IMPORTANCE_HIGH ;
203
+ }
204
+ this .startForeground (notificationId , createNotification (callInvite , notificationId , importance ));
205
+ }
206
+
207
+ /*
208
+ * Send the CallInvite to the Activity. Start the activity if it is not running already.
209
+ */
210
+ private void sendCallInviteToActivity (CallInvite callInvite , int notificationId ) {
211
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .Q && !isAppVisible ()) {
212
+ return ;
213
+ }
214
+ Intent intent = new Intent (this , getMainActivityClass (this ));
215
+ intent .setAction (ACTION_INCOMING_CALL );
216
+ intent .putExtra (INCOMING_CALL_NOTIFICATION_ID , notificationId );
217
+ intent .putExtra (INCOMING_CALL_INVITE , callInvite );
218
+ intent .addFlags (Intent .FLAG_ACTIVITY_SINGLE_TOP );
219
+ intent .addFlags (Intent .FLAG_ACTIVITY_NEW_TASK );
220
+ this .startActivity (intent );
221
+ }
222
+
223
+ private boolean isAppVisible () {
224
+ return ProcessLifecycleOwner
225
+ .get ()
226
+ .getLifecycle ()
227
+ .getCurrentState ()
228
+ .isAtLeast (Lifecycle .State .STARTED );
229
+ }
230
+ }
0 commit comments